@jmruthers/pace-core 0.5.117 → 0.5.119
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/dist/{DataTable-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
- package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
- package/dist/{chunk-XN2LYHDI.js → chunk-B4GZ2BXO.js} +27 -8
- package/dist/{chunk-XN2LYHDI.js.map → chunk-B4GZ2BXO.js.map} +1 -1
- package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
- package/dist/chunk-BHWIUEYH.js.map +1 -0
- package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
- package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
- package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
- package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
- package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
- package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
- package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
- package/dist/{chunk-IZXS7RZK.js → chunk-TDNI6ZWL.js} +5 -5
- package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
- package/src/hooks/__tests__/index.unit.test.ts +223 -0
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
- package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
- package/src/hooks/useSecureDataAccess.test.ts +1 -0
- package/src/hooks/useSecureDataAccess.ts +43 -5
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/services/EventService.ts +1 -0
- package/src/services/__tests__/AuthService.test.ts +473 -0
- package/src/services/__tests__/EventService.test.ts +390 -0
- package/src/services/__tests__/InactivityService.test.ts +217 -0
- package/src/services/__tests__/OrganisationService.test.ts +371 -0
- package/dist/chunk-KA3PSVNV.js.map +0 -1
- package/src/components/DataTable/utils/debugTools.ts +0 -609
- package/src/rbac/testing/index.tsx +0 -340
- /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
- /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
- /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
- /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
- /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
- /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
- /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
- /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
- /package/dist/{chunk-IZXS7RZK.js.map → chunk-TDNI6ZWL.js.map} +0 -0
- /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file UnifiedTableBody Component Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Components/__tests__
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive test suite for UnifiedTableBody component following TEST_STANDARD.md.
|
|
8
|
+
* Tests cover rendering, virtualization, editing, hierarchical data, and user interactions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
13
|
+
import userEvent from '@testing-library/user-event';
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table';
|
|
16
|
+
import { renderHook } from '@testing-library/react';
|
|
17
|
+
import { UnifiedTableBody } from '../UnifiedTableBody';
|
|
18
|
+
import type { DataRecord } from '../../types';
|
|
19
|
+
import { createTestData, createTestColumns } from '../../__tests__/test-utils/dataFactories';
|
|
20
|
+
|
|
21
|
+
// Mock Button component
|
|
22
|
+
vi.mock('../../Button/Button', () => ({
|
|
23
|
+
Button: ({ children, onClick, size, variant, className, 'aria-label': ariaLabel }: any) => (
|
|
24
|
+
<button
|
|
25
|
+
onClick={onClick}
|
|
26
|
+
data-size={size}
|
|
27
|
+
data-variant={variant}
|
|
28
|
+
className={className}
|
|
29
|
+
aria-label={ariaLabel}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</button>
|
|
33
|
+
),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock Input component
|
|
37
|
+
vi.mock('../../Input/Input', () => ({
|
|
38
|
+
Input: React.forwardRef(({ type, value, onChange, className, placeholder }: any, ref: any) => (
|
|
39
|
+
<input
|
|
40
|
+
ref={ref}
|
|
41
|
+
type={type}
|
|
42
|
+
value={value}
|
|
43
|
+
onChange={onChange}
|
|
44
|
+
className={className}
|
|
45
|
+
placeholder={placeholder}
|
|
46
|
+
data-testid={`input-${type || 'text'}`}
|
|
47
|
+
/>
|
|
48
|
+
)),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Mock Select components
|
|
52
|
+
vi.mock('../../Select/Select', () => ({
|
|
53
|
+
Select: React.forwardRef(({ children, value, onValueChange, onOpenChange }: any, ref: any) => (
|
|
54
|
+
<div ref={ref} data-testid="select" data-value={value}>
|
|
55
|
+
{children}
|
|
56
|
+
<button onClick={() => onOpenChange?.(true)} data-testid="select-open">Open</button>
|
|
57
|
+
<button onClick={() => onValueChange?.('option1')} data-testid="select-change">Change</button>
|
|
58
|
+
</div>
|
|
59
|
+
)),
|
|
60
|
+
SelectTrigger: ({ children, className }: any) => (
|
|
61
|
+
<div data-testid="select-trigger" className={className}>{children}</div>
|
|
62
|
+
),
|
|
63
|
+
SelectValue: ({ placeholder }: any) => <div data-testid="select-value">{placeholder}</div>,
|
|
64
|
+
SelectContent: ({ children, searchable, searchPlaceholder }: any) => (
|
|
65
|
+
<div data-testid="select-content" data-searchable={searchable} data-placeholder={searchPlaceholder}>
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
),
|
|
69
|
+
SelectItem: ({ children, value }: any) => (
|
|
70
|
+
<div data-testid="select-item" data-value={value}>{children}</div>
|
|
71
|
+
),
|
|
72
|
+
SelectGroup: ({ children }: any) => <div data-testid="select-group">{children}</div>,
|
|
73
|
+
SelectLabel: ({ children }: any) => <div data-testid="select-label">{children}</div>,
|
|
74
|
+
SelectSeparator: () => <div data-testid="select-separator" />,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
// Mock lucide-react icons
|
|
78
|
+
vi.mock('lucide-react', async () => {
|
|
79
|
+
const actual = await vi.importActual('lucide-react');
|
|
80
|
+
return {
|
|
81
|
+
...actual,
|
|
82
|
+
ChevronUp: ({ className }: { className?: string }) => <span className={className} data-testid="chevron-up">↑</span>,
|
|
83
|
+
ChevronDown: ({ className }: { className?: string }) => <span className={className} data-testid="chevron-down">↓</span>,
|
|
84
|
+
ChevronRight: ({ className }: { className?: string }) => <span className={className} data-testid="chevron-right">→</span>,
|
|
85
|
+
Check: ({ className }: { className?: string }) => <span className={className} data-testid="check-icon">✓</span>,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Mock EmptyState component
|
|
90
|
+
vi.mock('../EmptyState', () => ({
|
|
91
|
+
EmptyState: ({ title, description, isFiltered, onClearFilters }: any) => (
|
|
92
|
+
<div data-testid="empty-state">
|
|
93
|
+
{title && <div>{title}</div>}
|
|
94
|
+
{description && <div>{description}</div>}
|
|
95
|
+
{isFiltered && onClearFilters && (
|
|
96
|
+
<button onClick={onClearFilters} data-testid="clear-filters">Clear Filters</button>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
),
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// Mock FilterRow component
|
|
103
|
+
vi.mock('../FilterRow', () => ({
|
|
104
|
+
FilterRow: ({ table }: any) => <div data-testid="filter-row">Filter Row</div>,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
// Mock ActionButtons component
|
|
108
|
+
vi.mock('../ActionButtons', () => ({
|
|
109
|
+
ActionButtons: ({ row, actions, isEditing }: any) => (
|
|
110
|
+
<div data-testid="action-buttons">
|
|
111
|
+
{actions?.map((action: any, idx: number) => (
|
|
112
|
+
<button key={idx} onClick={() => action.onClick?.(row.original)} data-testid={`action-${action.testId || idx}`}>
|
|
113
|
+
{action.label}
|
|
114
|
+
</button>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
),
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
// Mock EditableRow component
|
|
121
|
+
vi.mock('../EditableRow', () => ({
|
|
122
|
+
EditableRow: ({ row, editingData, onEditingDataChange, onSave, onCancel }: any) => (
|
|
123
|
+
<tr data-testid="editable-row">
|
|
124
|
+
<td>
|
|
125
|
+
<input
|
|
126
|
+
data-testid="editable-input"
|
|
127
|
+
value={editingData?.name || ''}
|
|
128
|
+
onChange={(e) => onEditingDataChange?.({ ...editingData, name: e.target.value })}
|
|
129
|
+
/>
|
|
130
|
+
<button onClick={onSave} data-testid="save-editable">Save</button>
|
|
131
|
+
<button onClick={onCancel} data-testid="cancel-editable">Cancel</button>
|
|
132
|
+
</td>
|
|
133
|
+
</tr>
|
|
134
|
+
),
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
// Mock virtualizer
|
|
138
|
+
vi.mock('@tanstack/react-virtual', () => ({
|
|
139
|
+
useVirtualizer: vi.fn(() => ({
|
|
140
|
+
getVirtualItems: vi.fn(() => []),
|
|
141
|
+
getTotalSize: vi.fn(() => 0),
|
|
142
|
+
scrollToIndex: vi.fn(),
|
|
143
|
+
scrollToOffset: vi.fn(),
|
|
144
|
+
})),
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
interface TestData extends DataRecord {
|
|
148
|
+
id: string;
|
|
149
|
+
name: string;
|
|
150
|
+
email: string;
|
|
151
|
+
age: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
describe('[component] UnifiedTableBody', () => {
|
|
155
|
+
const mockData = createTestData(5);
|
|
156
|
+
const mockColumns = createTestColumns();
|
|
157
|
+
|
|
158
|
+
const createMockTable = (data: any[] = mockData, columns: any[] = mockColumns) => {
|
|
159
|
+
const { result } = renderHook(() =>
|
|
160
|
+
useReactTable({
|
|
161
|
+
data,
|
|
162
|
+
columns,
|
|
163
|
+
getCoreRowModel: getCoreRowModel(),
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
return result.current;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const defaultProps = {
|
|
170
|
+
table: createMockTable(),
|
|
171
|
+
isCreating: false,
|
|
172
|
+
creationData: {},
|
|
173
|
+
onCreationDataChange: vi.fn(),
|
|
174
|
+
onSaveCreation: vi.fn(),
|
|
175
|
+
onCancelCreation: vi.fn(),
|
|
176
|
+
editingRowId: null,
|
|
177
|
+
editingData: {},
|
|
178
|
+
onEditingDataChange: vi.fn(),
|
|
179
|
+
onSaveEditing: vi.fn(),
|
|
180
|
+
onCancelEditing: vi.fn(),
|
|
181
|
+
grouping: [],
|
|
182
|
+
aggregates: [],
|
|
183
|
+
getRowId: (row: any) => row.id || String(row.name),
|
|
184
|
+
dataLength: mockData.length,
|
|
185
|
+
virtualHeight: 600,
|
|
186
|
+
forceVirtualization: false,
|
|
187
|
+
actions: [],
|
|
188
|
+
rbac: { pageId: 'test-page' },
|
|
189
|
+
permissions: {
|
|
190
|
+
canRead: { can: true, isLoading: false },
|
|
191
|
+
canCreate: { can: true, isLoading: false },
|
|
192
|
+
canUpdate: { can: true, isLoading: false },
|
|
193
|
+
canDelete: { can: true, isLoading: false },
|
|
194
|
+
canExport: { can: true, isLoading: false },
|
|
195
|
+
canImport: { can: true, isLoading: false },
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
vi.clearAllMocks();
|
|
201
|
+
// Suppress console.log for cleaner test output
|
|
202
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
203
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
afterEach(() => {
|
|
207
|
+
vi.restoreAllMocks();
|
|
208
|
+
vi.clearAllMocks();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('Rendering', () => {
|
|
212
|
+
it('renders table body with rows', () => {
|
|
213
|
+
const table = createMockTable();
|
|
214
|
+
render(
|
|
215
|
+
<table>
|
|
216
|
+
<UnifiedTableBody {...defaultProps} table={table} />
|
|
217
|
+
</table>
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const tbody = screen.getByRole('rowgroup');
|
|
221
|
+
expect(tbody).toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('renders empty state when no rows', () => {
|
|
225
|
+
const table = createMockTable([]);
|
|
226
|
+
render(
|
|
227
|
+
<table>
|
|
228
|
+
<UnifiedTableBody {...defaultProps} table={table} dataLength={0} />
|
|
229
|
+
</table>
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(screen.getByTestId('empty-state')).toBeInTheDocument();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('renders custom empty state configuration', () => {
|
|
236
|
+
const table = createMockTable([]);
|
|
237
|
+
const emptyState = {
|
|
238
|
+
title: 'No Records',
|
|
239
|
+
description: 'Add some data',
|
|
240
|
+
};
|
|
241
|
+
render(
|
|
242
|
+
<table>
|
|
243
|
+
<UnifiedTableBody
|
|
244
|
+
{...defaultProps}
|
|
245
|
+
table={table}
|
|
246
|
+
dataLength={0}
|
|
247
|
+
emptyState={emptyState}
|
|
248
|
+
/>
|
|
249
|
+
</table>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
expect(screen.getByText('No Records')).toBeInTheDocument();
|
|
253
|
+
expect(screen.getByText('Add some data')).toBeInTheDocument();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('renders filter row when enabled', () => {
|
|
257
|
+
const table = createMockTable();
|
|
258
|
+
render(
|
|
259
|
+
<table>
|
|
260
|
+
<UnifiedTableBody
|
|
261
|
+
{...defaultProps}
|
|
262
|
+
table={table}
|
|
263
|
+
enableFiltering={true}
|
|
264
|
+
showFilterRow={true}
|
|
265
|
+
/>
|
|
266
|
+
</table>
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(screen.getByTestId('filter-row')).toBeInTheDocument();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Row Creation', () => {
|
|
274
|
+
it('renders creation row when isCreating is true', () => {
|
|
275
|
+
const table = createMockTable();
|
|
276
|
+
render(
|
|
277
|
+
<table>
|
|
278
|
+
<UnifiedTableBody
|
|
279
|
+
{...defaultProps}
|
|
280
|
+
table={table}
|
|
281
|
+
isCreating={true}
|
|
282
|
+
creationData={{ name: 'New User', email: 'new@example.com' }}
|
|
283
|
+
/>
|
|
284
|
+
</table>
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Check that inputs are rendered (by placeholder or value)
|
|
288
|
+
const nameInput = screen.getByDisplayValue('New User');
|
|
289
|
+
expect(nameInput).toBeInTheDocument();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('calls onCreationDataChange when creation input changes', async () => {
|
|
293
|
+
const user = userEvent.setup();
|
|
294
|
+
const onCreationDataChange = vi.fn();
|
|
295
|
+
const table = createMockTable();
|
|
296
|
+
|
|
297
|
+
render(
|
|
298
|
+
<table>
|
|
299
|
+
<UnifiedTableBody
|
|
300
|
+
{...defaultProps}
|
|
301
|
+
table={table}
|
|
302
|
+
isCreating={true}
|
|
303
|
+
creationData={{ name: '' }}
|
|
304
|
+
onCreationDataChange={onCreationDataChange}
|
|
305
|
+
/>
|
|
306
|
+
</table>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Find input by placeholder since mock might not have testid
|
|
310
|
+
const input = screen.getByPlaceholderText(/Enter Name/i);
|
|
311
|
+
await user.type(input, 'New Name');
|
|
312
|
+
|
|
313
|
+
expect(onCreationDataChange).toHaveBeenCalled();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('calls onSaveCreation when save button is clicked', async () => {
|
|
317
|
+
const user = userEvent.setup();
|
|
318
|
+
const onSaveCreation = vi.fn();
|
|
319
|
+
const table = createMockTable();
|
|
320
|
+
|
|
321
|
+
render(
|
|
322
|
+
<table>
|
|
323
|
+
<UnifiedTableBody
|
|
324
|
+
{...defaultProps}
|
|
325
|
+
table={table}
|
|
326
|
+
isCreating={true}
|
|
327
|
+
creationData={{ name: 'New User' }}
|
|
328
|
+
onSaveCreation={onSaveCreation}
|
|
329
|
+
/>
|
|
330
|
+
</table>
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
const saveButton = screen.getByTitle('Save new row');
|
|
334
|
+
await user.click(saveButton);
|
|
335
|
+
|
|
336
|
+
expect(onSaveCreation).toHaveBeenCalledTimes(1);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('calls onCancelCreation when cancel button is clicked', async () => {
|
|
340
|
+
const user = userEvent.setup();
|
|
341
|
+
const onCancelCreation = vi.fn();
|
|
342
|
+
const table = createMockTable();
|
|
343
|
+
|
|
344
|
+
render(
|
|
345
|
+
<table>
|
|
346
|
+
<UnifiedTableBody
|
|
347
|
+
{...defaultProps}
|
|
348
|
+
table={table}
|
|
349
|
+
isCreating={true}
|
|
350
|
+
creationData={{ name: 'New User' }}
|
|
351
|
+
onCancelCreation={onCancelCreation}
|
|
352
|
+
/>
|
|
353
|
+
</table>
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const cancelButton = screen.getByTitle('Cancel new row');
|
|
357
|
+
await user.click(cancelButton);
|
|
358
|
+
|
|
359
|
+
expect(onCancelCreation).toHaveBeenCalledTimes(1);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('renders select field for select column type in creation mode', () => {
|
|
363
|
+
const columnsWithSelect = [
|
|
364
|
+
{
|
|
365
|
+
id: 'status',
|
|
366
|
+
header: 'Status',
|
|
367
|
+
accessorKey: 'status',
|
|
368
|
+
fieldType: 'select' as const,
|
|
369
|
+
fieldOptions: [
|
|
370
|
+
{ value: 'active', label: 'Active' },
|
|
371
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
const table = createMockTable(mockData, columnsWithSelect);
|
|
376
|
+
|
|
377
|
+
render(
|
|
378
|
+
<table>
|
|
379
|
+
<UnifiedTableBody
|
|
380
|
+
{...defaultProps}
|
|
381
|
+
table={table}
|
|
382
|
+
isCreating={true}
|
|
383
|
+
creationData={{ status: 'active' }}
|
|
384
|
+
/>
|
|
385
|
+
</table>
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('renders number input for number field type in creation mode', () => {
|
|
392
|
+
const columnsWithNumber = [
|
|
393
|
+
{
|
|
394
|
+
id: 'age',
|
|
395
|
+
header: 'Age',
|
|
396
|
+
accessorKey: 'age',
|
|
397
|
+
fieldType: 'number' as const,
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
const table = createMockTable(mockData, columnsWithNumber);
|
|
401
|
+
|
|
402
|
+
render(
|
|
403
|
+
<table>
|
|
404
|
+
<UnifiedTableBody
|
|
405
|
+
{...defaultProps}
|
|
406
|
+
table={table}
|
|
407
|
+
isCreating={true}
|
|
408
|
+
creationData={{ age: '25' }}
|
|
409
|
+
/>
|
|
410
|
+
</table>
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// Find by type since the actual Input component is rendered
|
|
414
|
+
const numberInput = screen.getByDisplayValue('25');
|
|
415
|
+
expect(numberInput).toHaveAttribute('type', 'number');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('renders date input for date field type in creation mode', () => {
|
|
419
|
+
const columnsWithDate = [
|
|
420
|
+
{
|
|
421
|
+
id: 'createdAt',
|
|
422
|
+
header: 'Created At',
|
|
423
|
+
accessorKey: 'createdAt',
|
|
424
|
+
fieldType: 'date' as const,
|
|
425
|
+
},
|
|
426
|
+
];
|
|
427
|
+
const table = createMockTable(mockData, columnsWithDate);
|
|
428
|
+
|
|
429
|
+
render(
|
|
430
|
+
<table>
|
|
431
|
+
<UnifiedTableBody
|
|
432
|
+
{...defaultProps}
|
|
433
|
+
table={table}
|
|
434
|
+
isCreating={true}
|
|
435
|
+
creationData={{ createdAt: '2024-01-01' }}
|
|
436
|
+
/>
|
|
437
|
+
</table>
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// Find by type and value since the actual Input component is rendered
|
|
441
|
+
const dateInput = screen.getByDisplayValue('2024-01-01');
|
|
442
|
+
expect(dateInput).toHaveAttribute('type', 'date');
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe('Row Editing', () => {
|
|
447
|
+
it('renders EditableRow when row is being edited', () => {
|
|
448
|
+
const table = createMockTable();
|
|
449
|
+
const rowId = mockData[0].id;
|
|
450
|
+
|
|
451
|
+
render(
|
|
452
|
+
<table>
|
|
453
|
+
<UnifiedTableBody
|
|
454
|
+
{...defaultProps}
|
|
455
|
+
table={table}
|
|
456
|
+
editingRowId={rowId}
|
|
457
|
+
editingData={{ name: 'Edited Name' }}
|
|
458
|
+
/>
|
|
459
|
+
</table>
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
expect(screen.getByTestId('editable-row')).toBeInTheDocument();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('calls onEditingDataChange when editing data changes', async () => {
|
|
466
|
+
const user = userEvent.setup();
|
|
467
|
+
const onEditingDataChange = vi.fn();
|
|
468
|
+
const table = createMockTable();
|
|
469
|
+
const rowId = mockData[0].id;
|
|
470
|
+
|
|
471
|
+
render(
|
|
472
|
+
<table>
|
|
473
|
+
<UnifiedTableBody
|
|
474
|
+
{...defaultProps}
|
|
475
|
+
table={table}
|
|
476
|
+
editingRowId={rowId}
|
|
477
|
+
editingData={{ name: 'Current Name' }}
|
|
478
|
+
onEditingDataChange={onEditingDataChange}
|
|
479
|
+
/>
|
|
480
|
+
</table>
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const editableInput = screen.getByTestId('editable-input');
|
|
484
|
+
await user.clear(editableInput);
|
|
485
|
+
await user.type(editableInput, 'New Name');
|
|
486
|
+
|
|
487
|
+
expect(onEditingDataChange).toHaveBeenCalled();
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('calls onSaveEditing when save button is clicked in edit mode', async () => {
|
|
491
|
+
const user = userEvent.setup();
|
|
492
|
+
const onSaveEditing = vi.fn();
|
|
493
|
+
const table = createMockTable();
|
|
494
|
+
const rowId = mockData[0].id;
|
|
495
|
+
|
|
496
|
+
render(
|
|
497
|
+
<table>
|
|
498
|
+
<UnifiedTableBody
|
|
499
|
+
{...defaultProps}
|
|
500
|
+
table={table}
|
|
501
|
+
editingRowId={rowId}
|
|
502
|
+
editingData={{ name: 'Edited Name' }}
|
|
503
|
+
onSaveEditing={onSaveEditing}
|
|
504
|
+
/>
|
|
505
|
+
</table>
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const saveButton = screen.getByTestId('save-editable');
|
|
509
|
+
await user.click(saveButton);
|
|
510
|
+
|
|
511
|
+
expect(onSaveEditing).toHaveBeenCalledTimes(1);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('calls onCancelEditing when cancel button is clicked in edit mode', async () => {
|
|
515
|
+
const user = userEvent.setup();
|
|
516
|
+
const onCancelEditing = vi.fn();
|
|
517
|
+
const table = createMockTable();
|
|
518
|
+
const rowId = mockData[0].id;
|
|
519
|
+
|
|
520
|
+
render(
|
|
521
|
+
<table>
|
|
522
|
+
<UnifiedTableBody
|
|
523
|
+
{...defaultProps}
|
|
524
|
+
table={table}
|
|
525
|
+
editingRowId={rowId}
|
|
526
|
+
editingData={{ name: 'Edited Name' }}
|
|
527
|
+
onCancelEditing={onCancelEditing}
|
|
528
|
+
/>
|
|
529
|
+
</table>
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
const cancelButton = screen.getByTestId('cancel-editable');
|
|
533
|
+
await user.click(cancelButton);
|
|
534
|
+
|
|
535
|
+
expect(onCancelEditing).toHaveBeenCalledTimes(1);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('Grouped Rows', () => {
|
|
540
|
+
it('renders group header row when grouping is enabled', () => {
|
|
541
|
+
const table = createMockTable();
|
|
542
|
+
// Mock grouped row
|
|
543
|
+
const mockGroupedRow = {
|
|
544
|
+
...table.getRowModel().rows[0],
|
|
545
|
+
getIsGrouped: () => true,
|
|
546
|
+
getValue: (columnId: string) => 'Group A',
|
|
547
|
+
subRows: table.getRowModel().rows,
|
|
548
|
+
toggleExpanded: vi.fn(),
|
|
549
|
+
getIsExpanded: () => false,
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const tableWithGrouped = {
|
|
553
|
+
...table,
|
|
554
|
+
getRowModel: () => ({
|
|
555
|
+
rows: [mockGroupedRow],
|
|
556
|
+
}),
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
render(
|
|
560
|
+
<table>
|
|
561
|
+
<UnifiedTableBody
|
|
562
|
+
{...defaultProps}
|
|
563
|
+
table={tableWithGrouped as any}
|
|
564
|
+
grouping={['department']}
|
|
565
|
+
/>
|
|
566
|
+
</table>
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
// Group header should be rendered
|
|
570
|
+
expect(screen.getByText(/Group A/)).toBeInTheDocument();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('toggles group expansion when group header is clicked', async () => {
|
|
574
|
+
const user = userEvent.setup();
|
|
575
|
+
const toggleExpanded = vi.fn();
|
|
576
|
+
const table = createMockTable();
|
|
577
|
+
const mockGroupedRow = {
|
|
578
|
+
...table.getRowModel().rows[0],
|
|
579
|
+
getIsGrouped: () => true,
|
|
580
|
+
getValue: (columnId: string) => 'Group A',
|
|
581
|
+
subRows: table.getRowModel().rows,
|
|
582
|
+
toggleExpanded,
|
|
583
|
+
getIsExpanded: () => false,
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const tableWithGrouped = {
|
|
587
|
+
...table,
|
|
588
|
+
getRowModel: () => ({
|
|
589
|
+
rows: [mockGroupedRow],
|
|
590
|
+
}),
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
render(
|
|
594
|
+
<table>
|
|
595
|
+
<UnifiedTableBody
|
|
596
|
+
{...defaultProps}
|
|
597
|
+
table={tableWithGrouped as any}
|
|
598
|
+
grouping={['department']}
|
|
599
|
+
/>
|
|
600
|
+
</table>
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
const expandButton = screen.getByRole('button');
|
|
604
|
+
await user.click(expandButton);
|
|
605
|
+
|
|
606
|
+
expect(toggleExpanded).toHaveBeenCalled();
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe('Hierarchical Data', () => {
|
|
611
|
+
it('renders hierarchical rows with indentation', () => {
|
|
612
|
+
const hierarchicalData = [
|
|
613
|
+
{ id: '1', name: 'Parent', isParent: true },
|
|
614
|
+
{ id: '2', name: 'Child', isParent: false, parentId: '1' },
|
|
615
|
+
];
|
|
616
|
+
const table = createMockTable(hierarchicalData as any);
|
|
617
|
+
|
|
618
|
+
const hierarchicalState = {
|
|
619
|
+
isExpanded: vi.fn((id: string) => id === '1'),
|
|
620
|
+
hasChildren: vi.fn((id: string) => id === '1'),
|
|
621
|
+
getChildrenCount: vi.fn((id: string) => id === '1' ? 1 : 0),
|
|
622
|
+
toggleRow: vi.fn(),
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
render(
|
|
626
|
+
<table>
|
|
627
|
+
<UnifiedTableBody
|
|
628
|
+
{...defaultProps}
|
|
629
|
+
table={table}
|
|
630
|
+
hierarchical={{
|
|
631
|
+
enabled: true,
|
|
632
|
+
indentSize: 24,
|
|
633
|
+
state: hierarchicalState,
|
|
634
|
+
}}
|
|
635
|
+
/>
|
|
636
|
+
</table>
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
const tbody = screen.getByRole('rowgroup');
|
|
640
|
+
expect(tbody).toBeInTheDocument();
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('renders expansion button for parent rows', () => {
|
|
644
|
+
const hierarchicalData = [
|
|
645
|
+
{ id: '1', name: 'Parent', isParent: true },
|
|
646
|
+
];
|
|
647
|
+
const table = createMockTable(hierarchicalData as any);
|
|
648
|
+
|
|
649
|
+
const hierarchicalState = {
|
|
650
|
+
isExpanded: vi.fn(() => false),
|
|
651
|
+
hasChildren: vi.fn(() => true),
|
|
652
|
+
getChildrenCount: vi.fn(() => 1),
|
|
653
|
+
toggleRow: vi.fn(),
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
render(
|
|
657
|
+
<table>
|
|
658
|
+
<UnifiedTableBody
|
|
659
|
+
{...defaultProps}
|
|
660
|
+
table={table}
|
|
661
|
+
hierarchical={{
|
|
662
|
+
enabled: true,
|
|
663
|
+
indentSize: 24,
|
|
664
|
+
state: hierarchicalState,
|
|
665
|
+
}}
|
|
666
|
+
/>
|
|
667
|
+
</table>
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
// Expansion button should be rendered (ChevronRight icon)
|
|
671
|
+
const buttons = screen.getAllByRole('button');
|
|
672
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
describe('Action Buttons', () => {
|
|
677
|
+
it('renders action buttons for each row when actions column exists', () => {
|
|
678
|
+
// Create table with actions column
|
|
679
|
+
const columnsWithActions = [
|
|
680
|
+
...mockColumns,
|
|
681
|
+
{
|
|
682
|
+
id: 'actions',
|
|
683
|
+
header: 'Actions',
|
|
684
|
+
cell: () => null,
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
const table = createMockTable(mockData, columnsWithActions);
|
|
688
|
+
const actions = [
|
|
689
|
+
{ label: 'Edit', onClick: vi.fn(), testId: 'edit' },
|
|
690
|
+
{ label: 'Delete', onClick: vi.fn(), testId: 'delete' },
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
render(
|
|
694
|
+
<table>
|
|
695
|
+
<UnifiedTableBody
|
|
696
|
+
{...defaultProps}
|
|
697
|
+
table={table}
|
|
698
|
+
actions={actions}
|
|
699
|
+
/>
|
|
700
|
+
</table>
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
// ActionButtons mock renders with action- prefix
|
|
704
|
+
// Multiple rows mean multiple buttons, so use getAllByTestId
|
|
705
|
+
const editButtons = screen.getAllByTestId('action-edit');
|
|
706
|
+
const deleteButtons = screen.getAllByTestId('action-delete');
|
|
707
|
+
expect(editButtons.length).toBeGreaterThan(0);
|
|
708
|
+
expect(deleteButtons.length).toBeGreaterThan(0);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('calls action onClick when action button is clicked', async () => {
|
|
712
|
+
const user = userEvent.setup();
|
|
713
|
+
const onEditClick = vi.fn();
|
|
714
|
+
// Create table with actions column
|
|
715
|
+
const columnsWithActions = [
|
|
716
|
+
...mockColumns,
|
|
717
|
+
{
|
|
718
|
+
id: 'actions',
|
|
719
|
+
header: 'Actions',
|
|
720
|
+
cell: () => null,
|
|
721
|
+
},
|
|
722
|
+
];
|
|
723
|
+
const table = createMockTable(mockData, columnsWithActions);
|
|
724
|
+
const actions = [
|
|
725
|
+
{ label: 'Edit', onClick: onEditClick, testId: 'edit' },
|
|
726
|
+
];
|
|
727
|
+
|
|
728
|
+
render(
|
|
729
|
+
<table>
|
|
730
|
+
<UnifiedTableBody
|
|
731
|
+
{...defaultProps}
|
|
732
|
+
table={table}
|
|
733
|
+
actions={actions}
|
|
734
|
+
/>
|
|
735
|
+
</table>
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// ActionButtons mock renders with action- prefix
|
|
739
|
+
// Multiple rows mean multiple buttons, so use getAllByTestId and click the first one
|
|
740
|
+
const editButtons = screen.getAllByTestId('action-edit');
|
|
741
|
+
expect(editButtons.length).toBeGreaterThan(0);
|
|
742
|
+
await user.click(editButtons[0]);
|
|
743
|
+
|
|
744
|
+
expect(onEditClick).toHaveBeenCalled();
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
describe('Empty State', () => {
|
|
749
|
+
it('shows clear filters button when filtered', async () => {
|
|
750
|
+
const user = userEvent.setup();
|
|
751
|
+
const onClearFilters = vi.fn();
|
|
752
|
+
const table = createMockTable([]);
|
|
753
|
+
|
|
754
|
+
render(
|
|
755
|
+
<table>
|
|
756
|
+
<UnifiedTableBody
|
|
757
|
+
{...defaultProps}
|
|
758
|
+
table={table}
|
|
759
|
+
dataLength={0}
|
|
760
|
+
isFiltered={true}
|
|
761
|
+
onClearFilters={onClearFilters}
|
|
762
|
+
/>
|
|
763
|
+
</table>
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
const clearButton = screen.getByTestId('clear-filters');
|
|
767
|
+
expect(clearButton).toBeInTheDocument();
|
|
768
|
+
|
|
769
|
+
await user.click(clearButton);
|
|
770
|
+
expect(onClearFilters).toHaveBeenCalledTimes(1);
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
describe('Virtualization', () => {
|
|
775
|
+
it('renders standard rows when virtualization is not needed', () => {
|
|
776
|
+
const table = createMockTable();
|
|
777
|
+
render(
|
|
778
|
+
<table>
|
|
779
|
+
<UnifiedTableBody
|
|
780
|
+
{...defaultProps}
|
|
781
|
+
table={table}
|
|
782
|
+
dataLength={5}
|
|
783
|
+
forceVirtualization={false}
|
|
784
|
+
/>
|
|
785
|
+
</table>
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
const tbody = screen.getByRole('rowgroup');
|
|
789
|
+
expect(tbody).toBeInTheDocument();
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('uses virtualization when data length exceeds threshold', () => {
|
|
793
|
+
const largeData = createTestData(1500);
|
|
794
|
+
const table = createMockTable(largeData);
|
|
795
|
+
|
|
796
|
+
render(
|
|
797
|
+
<table>
|
|
798
|
+
<UnifiedTableBody
|
|
799
|
+
{...defaultProps}
|
|
800
|
+
table={table}
|
|
801
|
+
dataLength={1500}
|
|
802
|
+
forceVirtualization={false}
|
|
803
|
+
/>
|
|
804
|
+
</table>
|
|
805
|
+
);
|
|
806
|
+
|
|
807
|
+
const tbody = screen.getByRole('rowgroup');
|
|
808
|
+
expect(tbody).toBeInTheDocument();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('forces virtualization when forceVirtualization is true', () => {
|
|
812
|
+
const table = createMockTable();
|
|
813
|
+
|
|
814
|
+
render(
|
|
815
|
+
<table>
|
|
816
|
+
<UnifiedTableBody
|
|
817
|
+
{...defaultProps}
|
|
818
|
+
table={table}
|
|
819
|
+
dataLength={5}
|
|
820
|
+
forceVirtualization={true}
|
|
821
|
+
/>
|
|
822
|
+
</table>
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
const tbody = screen.getByRole('rowgroup');
|
|
826
|
+
expect(tbody).toBeInTheDocument();
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
describe('Edge Cases', () => {
|
|
831
|
+
it('handles null values in creationData gracefully', () => {
|
|
832
|
+
const table = createMockTable();
|
|
833
|
+
render(
|
|
834
|
+
<table>
|
|
835
|
+
<UnifiedTableBody
|
|
836
|
+
{...defaultProps}
|
|
837
|
+
table={table}
|
|
838
|
+
isCreating={true}
|
|
839
|
+
creationData={{ name: null as any, email: null as any }}
|
|
840
|
+
/>
|
|
841
|
+
</table>
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
const tbody = screen.getByRole('rowgroup');
|
|
845
|
+
expect(tbody).toBeInTheDocument();
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('handles missing getRowId function', () => {
|
|
849
|
+
const table = createMockTable();
|
|
850
|
+
render(
|
|
851
|
+
<table>
|
|
852
|
+
<UnifiedTableBody
|
|
853
|
+
{...defaultProps}
|
|
854
|
+
table={table}
|
|
855
|
+
getRowId={undefined}
|
|
856
|
+
/>
|
|
857
|
+
</table>
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
const tbody = screen.getByRole('rowgroup');
|
|
861
|
+
expect(tbody).toBeInTheDocument();
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('handles empty actions array', () => {
|
|
865
|
+
const table = createMockTable();
|
|
866
|
+
render(
|
|
867
|
+
<table>
|
|
868
|
+
<UnifiedTableBody
|
|
869
|
+
{...defaultProps}
|
|
870
|
+
table={table}
|
|
871
|
+
actions={[]}
|
|
872
|
+
/>
|
|
873
|
+
</table>
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
const tbody = screen.getByRole('rowgroup');
|
|
877
|
+
expect(tbody).toBeInTheDocument();
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('handles columns with editAccessorKey', () => {
|
|
881
|
+
const columnsWithEditKey = [
|
|
882
|
+
{
|
|
883
|
+
id: 'name',
|
|
884
|
+
header: 'Name',
|
|
885
|
+
accessorKey: 'name',
|
|
886
|
+
editAccessorKey: 'fullName',
|
|
887
|
+
},
|
|
888
|
+
];
|
|
889
|
+
const table = createMockTable(mockData, columnsWithEditKey);
|
|
890
|
+
|
|
891
|
+
render(
|
|
892
|
+
<table>
|
|
893
|
+
<UnifiedTableBody
|
|
894
|
+
{...defaultProps}
|
|
895
|
+
table={table}
|
|
896
|
+
isCreating={true}
|
|
897
|
+
creationData={{ fullName: 'New Name' }}
|
|
898
|
+
/>
|
|
899
|
+
</table>
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
const tbody = screen.getByRole('rowgroup');
|
|
903
|
+
expect(tbody).toBeInTheDocument();
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
describe('SelectEditField Component', () => {
|
|
908
|
+
it('renders creatable select with create option when search term has no match', async () => {
|
|
909
|
+
const user = userEvent.setup();
|
|
910
|
+
const columnsWithCreatable = [
|
|
911
|
+
{
|
|
912
|
+
id: 'status',
|
|
913
|
+
header: 'Status',
|
|
914
|
+
fieldType: 'select' as const,
|
|
915
|
+
fieldOptions: [
|
|
916
|
+
{ value: 'active', label: 'Active' },
|
|
917
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
918
|
+
],
|
|
919
|
+
creatable: true,
|
|
920
|
+
onCreateNew: vi.fn().mockResolvedValue('new-status'),
|
|
921
|
+
},
|
|
922
|
+
];
|
|
923
|
+
const table = createMockTable(mockData, columnsWithCreatable);
|
|
924
|
+
|
|
925
|
+
render(
|
|
926
|
+
<table>
|
|
927
|
+
<UnifiedTableBody
|
|
928
|
+
{...defaultProps}
|
|
929
|
+
table={table}
|
|
930
|
+
isCreating={true}
|
|
931
|
+
creationData={{ status: '' }}
|
|
932
|
+
/>
|
|
933
|
+
</table>
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
const selectTrigger = screen.getByTestId('select-trigger');
|
|
937
|
+
expect(selectTrigger).toBeInTheDocument();
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it('renders select with groups', () => {
|
|
941
|
+
const columnsWithGroups = [
|
|
942
|
+
{
|
|
943
|
+
id: 'category',
|
|
944
|
+
header: 'Category',
|
|
945
|
+
fieldType: 'select' as const,
|
|
946
|
+
fieldOptions: [
|
|
947
|
+
{
|
|
948
|
+
type: 'group',
|
|
949
|
+
label: 'Group 1',
|
|
950
|
+
items: [
|
|
951
|
+
{ value: 'option1', label: 'Option 1' },
|
|
952
|
+
{ value: 'option2', label: 'Option 2' },
|
|
953
|
+
],
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
},
|
|
957
|
+
];
|
|
958
|
+
const table = createMockTable(mockData, columnsWithGroups);
|
|
959
|
+
|
|
960
|
+
render(
|
|
961
|
+
<table>
|
|
962
|
+
<UnifiedTableBody
|
|
963
|
+
{...defaultProps}
|
|
964
|
+
table={table}
|
|
965
|
+
isCreating={true}
|
|
966
|
+
creationData={{ category: '' }}
|
|
967
|
+
/>
|
|
968
|
+
</table>
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
it('renders select with separators', () => {
|
|
975
|
+
const columnsWithSeparators = [
|
|
976
|
+
{
|
|
977
|
+
id: 'status',
|
|
978
|
+
header: 'Status',
|
|
979
|
+
fieldType: 'select' as const,
|
|
980
|
+
fieldOptions: [
|
|
981
|
+
{ value: 'active', label: 'Active' },
|
|
982
|
+
{ type: 'separator' },
|
|
983
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
984
|
+
],
|
|
985
|
+
},
|
|
986
|
+
];
|
|
987
|
+
const table = createMockTable(mockData, columnsWithSeparators);
|
|
988
|
+
|
|
989
|
+
render(
|
|
990
|
+
<table>
|
|
991
|
+
<UnifiedTableBody
|
|
992
|
+
{...defaultProps}
|
|
993
|
+
table={table}
|
|
994
|
+
isCreating={true}
|
|
995
|
+
creationData={{ status: '' }}
|
|
996
|
+
/>
|
|
997
|
+
</table>
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
|