@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,122 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react'
|
|
2
|
+
import { useColumnResize } from '../../hooks/useColumnResize'
|
|
3
|
+
|
|
4
|
+
describe('useColumnResize', () => {
|
|
5
|
+
describe('initialization', () => {
|
|
6
|
+
it('should return empty widths by default', () => {
|
|
7
|
+
const { result } = renderHook(() => useColumnResize())
|
|
8
|
+
|
|
9
|
+
expect(result.current.columnWidths).toEqual({})
|
|
10
|
+
expect(result.current.isResizing).toBe(false)
|
|
11
|
+
expect(result.current.resizingColumn).toBe(null)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should use initial widths when provided', () => {
|
|
15
|
+
const initialWidths = { col1: 100, col2: 200 }
|
|
16
|
+
const { result } = renderHook(() =>
|
|
17
|
+
useColumnResize({ initialWidths })
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
expect(result.current.columnWidths).toEqual(initialWidths)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('getColumnWidth', () => {
|
|
25
|
+
it('should return resized width when column has been resized', () => {
|
|
26
|
+
const initialWidths = { col1: 150 }
|
|
27
|
+
const { result } = renderHook(() =>
|
|
28
|
+
useColumnResize({ enabled: true, initialWidths })
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
expect(result.current.getColumnWidth('col1')).toBe(150)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should return default width when column has not been resized', () => {
|
|
35
|
+
const { result } = renderHook(() =>
|
|
36
|
+
useColumnResize({ enabled: true })
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(result.current.getColumnWidth('col1', 100)).toBe(100)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should return default width when disabled', () => {
|
|
43
|
+
const initialWidths = { col1: 150 }
|
|
44
|
+
const { result } = renderHook(() =>
|
|
45
|
+
useColumnResize({ enabled: false, initialWidths })
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expect(result.current.getColumnWidth('col1', 100)).toBe(100)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('startResize', () => {
|
|
53
|
+
it('should set resizing state when starting resize', () => {
|
|
54
|
+
const { result } = renderHook(() =>
|
|
55
|
+
useColumnResize({ enabled: true })
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
act(() => {
|
|
59
|
+
result.current.startResize('col1', 100, 150)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(result.current.isResizing).toBe(true)
|
|
63
|
+
expect(result.current.resizingColumn).toBe('col1')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should not start resize when disabled', () => {
|
|
67
|
+
const { result } = renderHook(() =>
|
|
68
|
+
useColumnResize({ enabled: false })
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
act(() => {
|
|
72
|
+
result.current.startResize('col1', 100, 150)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(result.current.isResizing).toBe(false)
|
|
76
|
+
expect(result.current.resizingColumn).toBe(null)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('resetWidths', () => {
|
|
81
|
+
it('should reset all widths to empty', () => {
|
|
82
|
+
const onWidthChange = jest.fn()
|
|
83
|
+
const { result } = renderHook(() =>
|
|
84
|
+
useColumnResize({
|
|
85
|
+
enabled: true,
|
|
86
|
+
initialWidths: { col1: 100, col2: 200 },
|
|
87
|
+
onWidthChange,
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
expect(result.current.columnWidths).toEqual({ col1: 100, col2: 200 })
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.resetWidths()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
expect(result.current.columnWidths).toEqual({})
|
|
98
|
+
expect(onWidthChange).toHaveBeenCalledWith({})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('minWidth enforcement', () => {
|
|
103
|
+
it('should respect default minWidth of 50', () => {
|
|
104
|
+
const { result } = renderHook(() =>
|
|
105
|
+
useColumnResize({ enabled: true })
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// minWidth is enforced during mouse move, not in startResize
|
|
109
|
+
// The hook stores the minWidth and uses it during resize calculations
|
|
110
|
+
expect(result.current.columnWidths).toEqual({})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should accept custom minWidth', () => {
|
|
114
|
+
const { result } = renderHook(() =>
|
|
115
|
+
useColumnResize({ enabled: true, minWidth: 100 })
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Custom minWidth is stored internally
|
|
119
|
+
expect(result.current.columnWidths).toEqual({})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react'
|
|
2
|
+
import { useColumnVisibility } from '../../hooks/useColumnVisibility'
|
|
3
|
+
|
|
4
|
+
describe('useColumnVisibility', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Clear localStorage before each test
|
|
7
|
+
localStorage.clear()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('Initialization', () => {
|
|
11
|
+
it('should initialize with empty visibility config', () => {
|
|
12
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
13
|
+
|
|
14
|
+
expect(result.current.columnVisibility).toEqual({})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should initialize with default visibility config', () => {
|
|
18
|
+
const defaultVisibility = {
|
|
19
|
+
col1: true,
|
|
20
|
+
col2: false,
|
|
21
|
+
col3: true,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { result } = renderHook(() =>
|
|
25
|
+
useColumnVisibility({ defaultVisibility })
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(result.current.columnVisibility).toEqual(defaultVisibility)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should load from localStorage if persistKey provided', () => {
|
|
32
|
+
const storedConfig = {
|
|
33
|
+
col1: false,
|
|
34
|
+
col2: true,
|
|
35
|
+
}
|
|
36
|
+
localStorage.setItem(
|
|
37
|
+
'column-visibility-test-table',
|
|
38
|
+
JSON.stringify(storedConfig)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const { result } = renderHook(() =>
|
|
42
|
+
useColumnVisibility({ persistKey: 'test-table' })
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
expect(result.current.columnVisibility).toEqual(storedConfig)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should use default visibility if localStorage is invalid', () => {
|
|
49
|
+
localStorage.setItem('column-visibility-test-table', 'invalid-json')
|
|
50
|
+
|
|
51
|
+
const defaultVisibility = { col1: true }
|
|
52
|
+
const { result } = renderHook(() =>
|
|
53
|
+
useColumnVisibility({
|
|
54
|
+
defaultVisibility,
|
|
55
|
+
persistKey: 'test-table',
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(result.current.columnVisibility).toEqual(defaultVisibility)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should prioritize localStorage over default visibility', () => {
|
|
63
|
+
const storedConfig = { col1: false }
|
|
64
|
+
const defaultVisibility = { col1: true, col2: true }
|
|
65
|
+
|
|
66
|
+
localStorage.setItem(
|
|
67
|
+
'column-visibility-priority-test',
|
|
68
|
+
JSON.stringify(storedConfig)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const { result } = renderHook(() =>
|
|
72
|
+
useColumnVisibility({
|
|
73
|
+
defaultVisibility,
|
|
74
|
+
persistKey: 'priority-test',
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(result.current.columnVisibility).toEqual(storedConfig)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('Toggle Column', () => {
|
|
83
|
+
it('should toggle column visibility from true to false', () => {
|
|
84
|
+
const { result } = renderHook(() =>
|
|
85
|
+
useColumnVisibility({ defaultVisibility: { col1: true } })
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
act(() => {
|
|
89
|
+
result.current.toggleColumn('col1')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(result.current.columnVisibility.col1).toBe(false)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should toggle column visibility from false to true', () => {
|
|
96
|
+
const { result } = renderHook(() =>
|
|
97
|
+
useColumnVisibility({ defaultVisibility: { col1: false } })
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
act(() => {
|
|
101
|
+
result.current.toggleColumn('col1')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(result.current.columnVisibility.col1).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should toggle unset column to false (default is true)', () => {
|
|
108
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
109
|
+
|
|
110
|
+
act(() => {
|
|
111
|
+
result.current.toggleColumn('newColumn')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
expect(result.current.columnVisibility.newColumn).toBe(false)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should toggle multiple columns independently', () => {
|
|
118
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
119
|
+
|
|
120
|
+
act(() => {
|
|
121
|
+
result.current.toggleColumn('col1')
|
|
122
|
+
result.current.toggleColumn('col2')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect(result.current.columnVisibility).toEqual({
|
|
126
|
+
col1: false,
|
|
127
|
+
col2: false,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
act(() => {
|
|
131
|
+
result.current.toggleColumn('col1')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
expect(result.current.columnVisibility).toEqual({
|
|
135
|
+
col1: true,
|
|
136
|
+
col2: false,
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('Show/Hide Column', () => {
|
|
142
|
+
it('should show column', () => {
|
|
143
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
144
|
+
|
|
145
|
+
act(() => {
|
|
146
|
+
result.current.showColumn('col1')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
expect(result.current.columnVisibility.col1).toBe(true)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('should hide column', () => {
|
|
153
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
154
|
+
|
|
155
|
+
act(() => {
|
|
156
|
+
result.current.hideColumn('col1')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
expect(result.current.columnVisibility.col1).toBe(false)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should not change already shown column', () => {
|
|
163
|
+
const { result } = renderHook(() =>
|
|
164
|
+
useColumnVisibility({ defaultVisibility: { col1: true } })
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
act(() => {
|
|
168
|
+
result.current.showColumn('col1')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
expect(result.current.columnVisibility.col1).toBe(true)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should not change already hidden column', () => {
|
|
175
|
+
const { result } = renderHook(() =>
|
|
176
|
+
useColumnVisibility({ defaultVisibility: { col1: false } })
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
act(() => {
|
|
180
|
+
result.current.hideColumn('col1')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
expect(result.current.columnVisibility.col1).toBe(false)
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe('Show/Hide All Columns', () => {
|
|
188
|
+
it('should show all columns', () => {
|
|
189
|
+
const { result } = renderHook(() =>
|
|
190
|
+
useColumnVisibility({
|
|
191
|
+
defaultVisibility: {
|
|
192
|
+
col1: false,
|
|
193
|
+
col2: false,
|
|
194
|
+
col3: false,
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
act(() => {
|
|
200
|
+
result.current.showAllColumns()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
expect(result.current.columnVisibility).toEqual({})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should hide all columns', () => {
|
|
207
|
+
const { result } = renderHook(() =>
|
|
208
|
+
useColumnVisibility({
|
|
209
|
+
defaultVisibility: {
|
|
210
|
+
col1: true,
|
|
211
|
+
col2: true,
|
|
212
|
+
col3: true,
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
act(() => {
|
|
218
|
+
result.current.hideAllColumns()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(result.current.columnVisibility).toEqual({
|
|
222
|
+
col1: false,
|
|
223
|
+
col2: false,
|
|
224
|
+
col3: false,
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should hide all except specified columns', () => {
|
|
229
|
+
const { result } = renderHook(() =>
|
|
230
|
+
useColumnVisibility({
|
|
231
|
+
defaultVisibility: {
|
|
232
|
+
col1: true,
|
|
233
|
+
col2: true,
|
|
234
|
+
col3: true,
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
act(() => {
|
|
240
|
+
result.current.hideAllColumns(['col1', 'col3'])
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
expect(result.current.columnVisibility.col1).toBe(true)
|
|
244
|
+
expect(result.current.columnVisibility.col2).toBe(false)
|
|
245
|
+
expect(result.current.columnVisibility.col3).toBe(true)
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe('Reset Visibility', () => {
|
|
250
|
+
it('should reset to default visibility', () => {
|
|
251
|
+
const defaultVisibility = {
|
|
252
|
+
col1: true,
|
|
253
|
+
col2: false,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const { result } = renderHook(() =>
|
|
257
|
+
useColumnVisibility({ defaultVisibility })
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
act(() => {
|
|
261
|
+
result.current.toggleColumn('col1')
|
|
262
|
+
result.current.toggleColumn('col2')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
expect(result.current.columnVisibility).toEqual({
|
|
266
|
+
col1: false,
|
|
267
|
+
col2: true,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
act(() => {
|
|
271
|
+
result.current.resetVisibility()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
expect(result.current.columnVisibility).toEqual(defaultVisibility)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should reset to empty when no default provided', () => {
|
|
278
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
279
|
+
|
|
280
|
+
act(() => {
|
|
281
|
+
result.current.hideColumn('col1')
|
|
282
|
+
result.current.hideColumn('col2')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
act(() => {
|
|
286
|
+
result.current.resetVisibility()
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
expect(result.current.columnVisibility).toEqual({})
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('Is Column Visible', () => {
|
|
294
|
+
it('should return true for explicitly visible column', () => {
|
|
295
|
+
const { result } = renderHook(() =>
|
|
296
|
+
useColumnVisibility({ defaultVisibility: { col1: true } })
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
expect(result.current.isColumnVisible('col1')).toBe(true)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should return false for explicitly hidden column', () => {
|
|
303
|
+
const { result } = renderHook(() =>
|
|
304
|
+
useColumnVisibility({ defaultVisibility: { col1: false } })
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
expect(result.current.isColumnVisible('col1')).toBe(false)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('should return true for unset column (default visible)', () => {
|
|
311
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
312
|
+
|
|
313
|
+
expect(result.current.isColumnVisible('unknownColumn')).toBe(true)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('should update after toggling', () => {
|
|
317
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
318
|
+
|
|
319
|
+
expect(result.current.isColumnVisible('col1')).toBe(true)
|
|
320
|
+
|
|
321
|
+
act(() => {
|
|
322
|
+
result.current.toggleColumn('col1')
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
expect(result.current.isColumnVisible('col1')).toBe(false)
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
describe('Persistence', () => {
|
|
330
|
+
it('should persist to localStorage when persistKey provided', () => {
|
|
331
|
+
const { result } = renderHook(() =>
|
|
332
|
+
useColumnVisibility({ persistKey: 'persist-test' })
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
act(() => {
|
|
336
|
+
result.current.hideColumn('col1')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const stored = localStorage.getItem('column-visibility-persist-test')
|
|
340
|
+
expect(stored).toBeTruthy()
|
|
341
|
+
expect(JSON.parse(stored!)).toEqual({ col1: false })
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it('should update localStorage on changes', () => {
|
|
345
|
+
const { result } = renderHook(() =>
|
|
346
|
+
useColumnVisibility({ persistKey: 'update-test' })
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
act(() => {
|
|
350
|
+
result.current.hideColumn('col1')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
let stored = localStorage.getItem('column-visibility-update-test')
|
|
354
|
+
expect(JSON.parse(stored!)).toEqual({ col1: false })
|
|
355
|
+
|
|
356
|
+
act(() => {
|
|
357
|
+
result.current.showColumn('col2')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
stored = localStorage.getItem('column-visibility-update-test')
|
|
361
|
+
expect(JSON.parse(stored!)).toEqual({
|
|
362
|
+
col1: false,
|
|
363
|
+
col2: true,
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('should not persist when persistKey not provided', () => {
|
|
368
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
369
|
+
|
|
370
|
+
act(() => {
|
|
371
|
+
result.current.hideColumn('col1')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
expect(localStorage.length).toBe(0)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should persist across multiple operations', () => {
|
|
378
|
+
const { result } = renderHook(() =>
|
|
379
|
+
useColumnVisibility({ persistKey: 'multi-op-test' })
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
act(() => {
|
|
383
|
+
result.current.hideColumn('col1')
|
|
384
|
+
result.current.showColumn('col2')
|
|
385
|
+
result.current.toggleColumn('col3')
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
const stored = localStorage.getItem('column-visibility-multi-op-test')
|
|
389
|
+
expect(JSON.parse(stored!)).toEqual({
|
|
390
|
+
col1: false,
|
|
391
|
+
col2: true,
|
|
392
|
+
col3: false,
|
|
393
|
+
})
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
describe('Callback Integration', () => {
|
|
398
|
+
it('should call onVisibilityChange when visibility changes', () => {
|
|
399
|
+
const onVisibilityChange = jest.fn()
|
|
400
|
+
const { result } = renderHook(() =>
|
|
401
|
+
useColumnVisibility({ onVisibilityChange })
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
// Clear the initial call from useEffect
|
|
405
|
+
onVisibilityChange.mockClear()
|
|
406
|
+
|
|
407
|
+
act(() => {
|
|
408
|
+
result.current.toggleColumn('col1')
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
expect(onVisibilityChange).toHaveBeenCalled()
|
|
412
|
+
expect(onVisibilityChange.mock.calls[0][0]).toEqual({ col1: false })
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should call callback on initialization with persisted data', () => {
|
|
416
|
+
const persistedData = { col1: false }
|
|
417
|
+
localStorage.setItem(
|
|
418
|
+
'column-visibility-callback-test',
|
|
419
|
+
JSON.stringify(persistedData)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
const onVisibilityChange = jest.fn()
|
|
423
|
+
renderHook(() =>
|
|
424
|
+
useColumnVisibility({
|
|
425
|
+
persistKey: 'callback-test',
|
|
426
|
+
onVisibilityChange,
|
|
427
|
+
})
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
expect(onVisibilityChange).toHaveBeenCalledWith(persistedData)
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('should call callback multiple times for multiple changes', () => {
|
|
434
|
+
const onVisibilityChange = jest.fn()
|
|
435
|
+
const { result } = renderHook(() =>
|
|
436
|
+
useColumnVisibility({ onVisibilityChange })
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
act(() => {
|
|
440
|
+
result.current.hideColumn('col1')
|
|
441
|
+
result.current.showColumn('col2')
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
expect(onVisibilityChange).toHaveBeenCalledTimes(2)
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('Edge Cases', () => {
|
|
449
|
+
it('should handle undefined column IDs gracefully', () => {
|
|
450
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
451
|
+
|
|
452
|
+
expect(() => {
|
|
453
|
+
act(() => {
|
|
454
|
+
result.current.toggleColumn(undefined as any)
|
|
455
|
+
})
|
|
456
|
+
}).not.toThrow()
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it('should handle empty string column IDs', () => {
|
|
460
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
461
|
+
|
|
462
|
+
act(() => {
|
|
463
|
+
result.current.toggleColumn('')
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
expect(result.current.columnVisibility['']).toBe(false)
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it('should handle special characters in column IDs', () => {
|
|
470
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
471
|
+
|
|
472
|
+
act(() => {
|
|
473
|
+
result.current.toggleColumn('col-1.2.3')
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
expect(result.current.columnVisibility['col-1.2.3']).toBe(false)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it('should handle many columns', () => {
|
|
480
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
481
|
+
|
|
482
|
+
act(() => {
|
|
483
|
+
for (let i = 0; i < 100; i++) {
|
|
484
|
+
result.current.toggleColumn(`col${i}`)
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
expect(Object.keys(result.current.columnVisibility).length).toBe(100)
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
describe('Complex Scenarios', () => {
|
|
493
|
+
it('should handle mixed operations correctly', () => {
|
|
494
|
+
const { result } = renderHook(() =>
|
|
495
|
+
useColumnVisibility({
|
|
496
|
+
defaultVisibility: {
|
|
497
|
+
col1: true,
|
|
498
|
+
col2: false,
|
|
499
|
+
col3: true,
|
|
500
|
+
},
|
|
501
|
+
})
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
act(() => {
|
|
505
|
+
result.current.toggleColumn('col1')
|
|
506
|
+
result.current.showColumn('col2')
|
|
507
|
+
result.current.hideColumn('col3')
|
|
508
|
+
result.current.showColumn('col4')
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
expect(result.current.columnVisibility).toEqual({
|
|
512
|
+
col1: false,
|
|
513
|
+
col2: true,
|
|
514
|
+
col3: false,
|
|
515
|
+
col4: true,
|
|
516
|
+
})
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
it('should maintain state across re-renders', () => {
|
|
520
|
+
const { result, rerender } = renderHook(() => useColumnVisibility())
|
|
521
|
+
|
|
522
|
+
act(() => {
|
|
523
|
+
result.current.hideColumn('col1')
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
rerender()
|
|
527
|
+
|
|
528
|
+
expect(result.current.isColumnVisible('col1')).toBe(false)
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('should handle persistence with complex visibility state', () => {
|
|
532
|
+
const { result } = renderHook(() =>
|
|
533
|
+
useColumnVisibility({ persistKey: 'complex-test' })
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
const complexState = {
|
|
537
|
+
col1: true,
|
|
538
|
+
col2: false,
|
|
539
|
+
col3: true,
|
|
540
|
+
col4: false,
|
|
541
|
+
col5: true,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
act(() => {
|
|
545
|
+
result.current.setColumnVisibility(complexState)
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
const stored = localStorage.getItem('column-visibility-complex-test')
|
|
549
|
+
expect(JSON.parse(stored!)).toEqual(complexState)
|
|
550
|
+
|
|
551
|
+
// Verify loading in new hook instance
|
|
552
|
+
const { result: result2 } = renderHook(() =>
|
|
553
|
+
useColumnVisibility({ persistKey: 'complex-test' })
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
expect(result2.current.columnVisibility).toEqual(complexState)
|
|
557
|
+
})
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
describe('Set Column Visibility Directly', () => {
|
|
561
|
+
it('should set entire visibility config', () => {
|
|
562
|
+
const { result } = renderHook(() => useColumnVisibility())
|
|
563
|
+
|
|
564
|
+
const newConfig = {
|
|
565
|
+
col1: true,
|
|
566
|
+
col2: false,
|
|
567
|
+
col3: true,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
act(() => {
|
|
571
|
+
result.current.setColumnVisibility(newConfig)
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
expect(result.current.columnVisibility).toEqual(newConfig)
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
it('should replace previous config', () => {
|
|
578
|
+
const { result } = renderHook(() =>
|
|
579
|
+
useColumnVisibility({
|
|
580
|
+
defaultVisibility: {
|
|
581
|
+
col1: false,
|
|
582
|
+
col2: true,
|
|
583
|
+
},
|
|
584
|
+
})
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
act(() => {
|
|
588
|
+
result.current.setColumnVisibility({ col3: false })
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
expect(result.current.columnVisibility).toEqual({ col3: false })
|
|
592
|
+
})
|
|
593
|
+
})
|
|
594
|
+
})
|