@jmruthers/pace-core 0.5.111 → 0.5.112
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-5W2HVLLV.js → DataTable-3D3BUZDV.js} +8 -8
- package/dist/{UnifiedAuthProvider-LUM3QLS5.js → UnifiedAuthProvider-KZZUO27W.js} +3 -3
- package/dist/{api-SIZPFBFX.js → api-QPMBZZUZ.js} +3 -3
- package/dist/{audit-5JI5T3SL.js → audit-H4YJJF7R.js} +2 -2
- package/dist/{chunk-IWJYNWXN.js → chunk-3OGQLOJM.js} +11 -3
- package/dist/chunk-3OGQLOJM.js.map +1 -0
- package/dist/{chunk-TDFBX7KJ.js → chunk-7H75SHXZ.js} +2 -2
- package/dist/{chunk-EFVQBYFN.js → chunk-BUN7NMV7.js} +2 -2
- package/dist/{chunk-ACYQNYHB.js → chunk-C5RN4TE5.js} +7 -7
- package/dist/{chunk-2BIDKXQU.js → chunk-EKVVTPIF.js} +82 -23
- package/dist/chunk-EKVVTPIF.js.map +1 -0
- package/dist/{chunk-X7SPKHYZ.js → chunk-F6QB26OS.js} +4 -4
- package/dist/{chunk-UGVU7L7N.js → chunk-I7JC7PTJ.js} +6 -6
- package/dist/chunk-I7JC7PTJ.js.map +1 -0
- package/dist/{chunk-I5YM5BGS.js → chunk-L36JW4KV.js} +2 -2
- package/dist/{chunk-ZL45MG76.js → chunk-MNSGWRPB.js} +15 -15
- package/dist/{chunk-JE2GFA3O.js → chunk-NEONKMTU.js} +3 -3
- package/dist/{chunk-MW73E7SP.js → chunk-OO3V7W4H.js} +2 -2
- package/dist/chunk-OO3V7W4H.js.map +1 -0
- package/dist/{chunk-PXXS26G5.js → chunk-TAJRS6YB.js} +2 -2
- package/dist/{chunk-TD4BXGPE.js → chunk-WMPZY26G.js} +8 -4
- package/dist/{chunk-TD4BXGPE.js.map → chunk-WMPZY26G.js.map} +1 -1
- package/dist/components.js +10 -10
- package/dist/hooks.js +7 -7
- package/dist/index.js +13 -13
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +9 -9
- 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 +8 -8
- 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/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/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/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.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 +12 -12
- package/package.json +1 -1
- package/src/components/DataTable/DataTable.test.tsx +405 -154
- package/src/components/DataTable/components/DataTableCore.tsx +6 -1
- package/src/components/EventSelector/EventSelector.tsx +32 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +56 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +75 -12
- package/src/rbac/audit-enhanced.ts +14 -2
- package/src/rbac/audit.test.ts +16 -6
- package/src/rbac/audit.ts +11 -1
- package/src/rbac/hooks/usePermissions.ts +18 -2
- package/src/services/EventService.ts +3 -2
- package/dist/chunk-2BIDKXQU.js.map +0 -1
- package/dist/chunk-IWJYNWXN.js.map +0 -1
- package/dist/chunk-MW73E7SP.js.map +0 -1
- package/dist/chunk-UGVU7L7N.js.map +0 -1
- /package/dist/{DataTable-5W2HVLLV.js.map → DataTable-3D3BUZDV.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-LUM3QLS5.js.map → UnifiedAuthProvider-KZZUO27W.js.map} +0 -0
- /package/dist/{api-SIZPFBFX.js.map → api-QPMBZZUZ.js.map} +0 -0
- /package/dist/{audit-5JI5T3SL.js.map → audit-H4YJJF7R.js.map} +0 -0
- /package/dist/{chunk-TDFBX7KJ.js.map → chunk-7H75SHXZ.js.map} +0 -0
- /package/dist/{chunk-EFVQBYFN.js.map → chunk-BUN7NMV7.js.map} +0 -0
- /package/dist/{chunk-ACYQNYHB.js.map → chunk-C5RN4TE5.js.map} +0 -0
- /package/dist/{chunk-X7SPKHYZ.js.map → chunk-F6QB26OS.js.map} +0 -0
- /package/dist/{chunk-I5YM5BGS.js.map → chunk-L36JW4KV.js.map} +0 -0
- /package/dist/{chunk-ZL45MG76.js.map → chunk-MNSGWRPB.js.map} +0 -0
- /package/dist/{chunk-JE2GFA3O.js.map → chunk-NEONKMTU.js.map} +0 -0
- /package/dist/{chunk-PXXS26G5.js.map → chunk-TAJRS6YB.js.map} +0 -0
|
@@ -1,58 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DataTable Component Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/__tests__
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive test suite for DataTable wrapper component following testing guidelines.
|
|
8
|
+
* Tests cover feature normalization, validation logic, prop forwarding, and edge cases.
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import React from 'react';
|
|
2
|
-
import { render, screen, cleanup } from '@testing-library/react';
|
|
3
|
-
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import { render, screen, cleanup, waitFor } from '@testing-library/react';
|
|
13
|
+
import { vi, describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
4
14
|
import { DataTable } from './DataTable';
|
|
5
|
-
import { createTestData, createTestColumns } from './__tests__/test-utils/dataFactories';
|
|
6
|
-
import { createDefaultFeatures } from './__tests__/test-utils';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
import { createTestData, createTestColumns, testDataScenarios } from './__tests__/test-utils/dataFactories';
|
|
16
|
+
import { createDefaultFeatures, createFullFeatures, createFeaturesWithEnabled } from './__tests__/test-utils';
|
|
17
|
+
import type { DataTableFeatureConfig } from './types';
|
|
18
|
+
import { DataTableCore } from './components/DataTableCore';
|
|
19
|
+
|
|
20
|
+
// Mock DataTableCore to focus on wrapper logic
|
|
21
|
+
// Note: Must define mock inside factory due to hoisting
|
|
22
|
+
vi.mock('./components/DataTableCore', () => {
|
|
23
|
+
const mockDataTableCore = vi.fn(({ features, ...props }: any) => (
|
|
24
|
+
<div
|
|
25
|
+
data-testid="data-table-core"
|
|
26
|
+
data-features={JSON.stringify(features)}
|
|
27
|
+
data-title={props.title}
|
|
28
|
+
data-description={props.description}
|
|
29
|
+
data-variant={props.variant}
|
|
30
|
+
data-class-name={props.className}
|
|
31
|
+
data-page-id={props.rbac?.pageId}
|
|
32
|
+
data-page-name={props.rbac?.pageName}
|
|
33
|
+
>
|
|
12
34
|
Mock DataTableCore
|
|
13
35
|
</div>
|
|
14
|
-
)
|
|
15
|
-
|
|
36
|
+
));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
DataTableCore: mockDataTableCore,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
16
42
|
|
|
17
43
|
// Mock the logger utility
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
44
|
+
// Create a module-level mock logger that can be accessed in tests
|
|
45
|
+
const mockLogger = {
|
|
46
|
+
info: vi.fn(),
|
|
47
|
+
warn: vi.fn(),
|
|
48
|
+
error: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Note: Must define mock inside factory due to hoisting
|
|
52
|
+
vi.mock('../../utils/logger', () => {
|
|
53
|
+
return {
|
|
54
|
+
createLogger: () => mockLogger,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Store original console.log to spy on it
|
|
59
|
+
const originalConsoleLog = console.log;
|
|
60
|
+
const consoleLogSpy = vi.fn();
|
|
61
|
+
beforeAll(() => {
|
|
62
|
+
console.log = consoleLogSpy;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
console.log = originalConsoleLog;
|
|
67
|
+
});
|
|
30
68
|
|
|
31
69
|
describe('[component] DataTable', () => {
|
|
32
70
|
const testData = createTestData(3);
|
|
33
71
|
const testColumns = createTestColumns();
|
|
72
|
+
const defaultRBAC = { pageId: 'test-page-id' };
|
|
34
73
|
const defaultProps = {
|
|
35
74
|
data: testData,
|
|
36
75
|
columns: testColumns,
|
|
37
|
-
rbac:
|
|
38
|
-
permissions: ['read'],
|
|
39
|
-
accessLevel: 'read' as const,
|
|
40
|
-
},
|
|
76
|
+
rbac: defaultRBAC,
|
|
41
77
|
};
|
|
42
78
|
|
|
79
|
+
// Helper to get the mocked DataTableCore function
|
|
80
|
+
const getMockedDataTableCore = () => vi.mocked(DataTableCore);
|
|
81
|
+
|
|
43
82
|
beforeEach(() => {
|
|
44
83
|
vi.clearAllMocks();
|
|
84
|
+
consoleLogSpy.mockClear();
|
|
45
85
|
});
|
|
46
86
|
|
|
47
87
|
afterEach(() => {
|
|
48
88
|
cleanup();
|
|
49
89
|
});
|
|
50
90
|
|
|
51
|
-
describe('
|
|
52
|
-
it('renders DataTableCore with
|
|
91
|
+
describe('Rendering', () => {
|
|
92
|
+
it('renders DataTableCore with required props', () => {
|
|
53
93
|
render(<DataTable {...defaultProps} />);
|
|
54
94
|
|
|
55
95
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
96
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(1);
|
|
56
97
|
});
|
|
57
98
|
|
|
58
99
|
it('renders with title and description', () => {
|
|
@@ -64,256 +105,466 @@ describe('[component] DataTable', () => {
|
|
|
64
105
|
/>
|
|
65
106
|
);
|
|
66
107
|
|
|
67
|
-
|
|
108
|
+
const core = screen.getByTestId('data-table-core');
|
|
109
|
+
expect(core).toHaveAttribute('data-title', 'Test Table');
|
|
110
|
+
expect(core).toHaveAttribute('data-description', 'Test description');
|
|
68
111
|
});
|
|
69
112
|
|
|
70
113
|
it('renders with custom variant', () => {
|
|
71
114
|
render(<DataTable {...defaultProps} variant="compact" />);
|
|
72
115
|
|
|
73
|
-
|
|
116
|
+
const core = screen.getByTestId('data-table-core');
|
|
117
|
+
expect(core).toHaveAttribute('data-variant', 'compact');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('renders with custom className', () => {
|
|
121
|
+
render(<DataTable {...defaultProps} className="custom-table-class" />);
|
|
122
|
+
|
|
123
|
+
const core = screen.getByTestId('data-table-core');
|
|
124
|
+
expect(core).toHaveAttribute('data-class-name', 'custom-table-class');
|
|
74
125
|
});
|
|
75
126
|
});
|
|
76
127
|
|
|
77
128
|
describe('Feature Configuration', () => {
|
|
78
|
-
it('
|
|
129
|
+
it('normalizes undefined features to default disabled features', () => {
|
|
79
130
|
render(<DataTable {...defaultProps} />);
|
|
80
131
|
|
|
81
132
|
const core = screen.getByTestId('data-table-core');
|
|
82
|
-
|
|
133
|
+
const features = JSON.parse(core.getAttribute('data-features') || '{}');
|
|
134
|
+
|
|
135
|
+
expect(features).toMatchObject({
|
|
136
|
+
search: false,
|
|
137
|
+
pagination: false,
|
|
138
|
+
sorting: false,
|
|
139
|
+
filtering: false,
|
|
140
|
+
import: false,
|
|
141
|
+
export: false,
|
|
142
|
+
selection: false,
|
|
143
|
+
creation: false,
|
|
144
|
+
editing: false,
|
|
145
|
+
deletion: false,
|
|
146
|
+
deleteSelected: false,
|
|
147
|
+
grouping: false,
|
|
148
|
+
columnVisibility: false,
|
|
149
|
+
columnReordering: false,
|
|
150
|
+
hierarchical: false,
|
|
151
|
+
});
|
|
83
152
|
});
|
|
84
153
|
|
|
85
|
-
it('
|
|
86
|
-
const
|
|
87
|
-
|
|
154
|
+
it('normalizes partial features by merging with defaults', () => {
|
|
155
|
+
const partialFeatures: DataTableFeatureConfig = {
|
|
156
|
+
search: true,
|
|
157
|
+
pagination: true,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
render(<DataTable {...defaultProps} features={partialFeatures} />);
|
|
88
161
|
|
|
89
162
|
const core = screen.getByTestId('data-table-core');
|
|
90
|
-
|
|
163
|
+
const features = JSON.parse(core.getAttribute('data-features') || '{}');
|
|
164
|
+
|
|
165
|
+
expect(features.search).toBe(true);
|
|
166
|
+
expect(features.pagination).toBe(true);
|
|
167
|
+
expect(features.sorting).toBe(false); // Default value
|
|
168
|
+
expect(features.editing).toBe(false); // Default value
|
|
91
169
|
});
|
|
92
170
|
|
|
93
|
-
it('
|
|
94
|
-
const features =
|
|
171
|
+
it('passes fully normalized features to DataTableCore', () => {
|
|
172
|
+
const features = createFullFeatures();
|
|
173
|
+
render(<DataTable {...defaultProps} features={features} />);
|
|
174
|
+
|
|
175
|
+
const core = screen.getByTestId('data-table-core');
|
|
176
|
+
const normalizedFeatures = JSON.parse(core.getAttribute('data-features') || '{}');
|
|
177
|
+
|
|
178
|
+
expect(normalizedFeatures).toMatchObject({
|
|
95
179
|
search: true,
|
|
96
180
|
pagination: true,
|
|
97
181
|
sorting: true,
|
|
98
|
-
|
|
99
|
-
|
|
182
|
+
filtering: true,
|
|
183
|
+
import: true,
|
|
184
|
+
export: true,
|
|
185
|
+
selection: true,
|
|
186
|
+
creation: true,
|
|
187
|
+
editing: true,
|
|
188
|
+
deletion: true,
|
|
189
|
+
deleteSelected: true,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('memoizes normalized features when features prop does not change', () => {
|
|
194
|
+
const features = createDefaultFeatures();
|
|
195
|
+
const { rerender } = render(<DataTable {...defaultProps} features={features} />);
|
|
100
196
|
|
|
101
|
-
|
|
102
|
-
|
|
197
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(1);
|
|
198
|
+
|
|
199
|
+
rerender(<DataTable {...defaultProps} features={features} />);
|
|
200
|
+
|
|
201
|
+
// Should not re-normalize if features object reference is the same
|
|
202
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(2);
|
|
103
203
|
});
|
|
104
204
|
});
|
|
105
205
|
|
|
106
206
|
describe('RBAC Configuration', () => {
|
|
107
|
-
it('
|
|
207
|
+
it('passes rbac with pageId to DataTableCore', () => {
|
|
208
|
+
render(<DataTable {...defaultProps} rbac={{ pageId: 'custom-page-id' }} />);
|
|
209
|
+
|
|
210
|
+
const core = screen.getByTestId('data-table-core');
|
|
211
|
+
expect(core).toHaveAttribute('data-page-id', 'custom-page-id');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('passes rbac with pageName to DataTableCore', () => {
|
|
215
|
+
render(<DataTable {...defaultProps} rbac={{ pageName: 'custom-page-name' }} />);
|
|
216
|
+
|
|
217
|
+
const core = screen.getByTestId('data-table-core');
|
|
218
|
+
expect(core).toHaveAttribute('data-page-name', 'custom-page-name');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('Validation and Warnings', () => {
|
|
223
|
+
it('logs debug information in development mode', () => {
|
|
108
224
|
render(<DataTable {...defaultProps} />);
|
|
109
225
|
|
|
110
|
-
expect(
|
|
226
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
227
|
+
'[DataTable] 🎯 DataTable WRAPPER called:',
|
|
228
|
+
expect.objectContaining({
|
|
229
|
+
dataLength: testData.length,
|
|
230
|
+
columnsCount: testColumns.length,
|
|
231
|
+
pageName: defaultRBAC.pageId,
|
|
232
|
+
})
|
|
233
|
+
);
|
|
111
234
|
});
|
|
112
235
|
|
|
113
|
-
it('
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
render(<DataTable {...propsWithWrite} />);
|
|
236
|
+
it('logs info message when features are not provided in development', async () => {
|
|
237
|
+
// Note: vitest.config.ts sets import.meta.env.MODE to 'development' by default
|
|
238
|
+
// This test verifies the development warning when features are not provided
|
|
239
|
+
// Skip if not in development mode to avoid false failures
|
|
240
|
+
if (import.meta.env.MODE !== 'development') {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
122
243
|
|
|
123
|
-
|
|
244
|
+
// Clear mock before test to ensure clean state
|
|
245
|
+
mockLogger.info.mockClear();
|
|
246
|
+
|
|
247
|
+
// Render without features prop - should trigger the warning in development mode
|
|
248
|
+
render(<DataTable {...defaultProps} />);
|
|
249
|
+
|
|
250
|
+
// Wait for useEffect to run - it runs after render
|
|
251
|
+
// The effect checks: if (!features && import.meta.env?.MODE === 'development')
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
expect(mockLogger.info).toHaveBeenCalled();
|
|
254
|
+
}, {
|
|
255
|
+
timeout: 1000,
|
|
256
|
+
interval: 50
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Verify it was called with the correct message
|
|
260
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
261
|
+
'DataTable: no features provided; all capabilities default to disabled. Pass a features object to enable functionality.'
|
|
262
|
+
);
|
|
124
263
|
});
|
|
125
264
|
|
|
126
|
-
it('
|
|
127
|
-
|
|
128
|
-
...defaultProps,
|
|
129
|
-
rbac: {
|
|
130
|
-
permissions: ['read', 'write', 'admin'],
|
|
131
|
-
accessLevel: 'admin' as const,
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
render(<DataTable {...propsWithAdmin} />);
|
|
265
|
+
it('does not log info message when features are provided', () => {
|
|
266
|
+
render(<DataTable {...defaultProps} features={createDefaultFeatures()} />);
|
|
135
267
|
|
|
136
|
-
expect(
|
|
268
|
+
expect(mockLogger.info).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('warns when deleteSelected is enabled without deletion', () => {
|
|
272
|
+
const invalidFeatures = createFeaturesWithEnabled({
|
|
273
|
+
deleteSelected: true,
|
|
274
|
+
deletion: false,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
render(<DataTable {...defaultProps} features={invalidFeatures} />);
|
|
278
|
+
|
|
279
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
280
|
+
'deleteSelected requires deletion to be enabled'
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('does not warn when deleteSelected is enabled with deletion', () => {
|
|
285
|
+
const validFeatures = createFeaturesWithEnabled({
|
|
286
|
+
deleteSelected: true,
|
|
287
|
+
deletion: true,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
render(<DataTable {...defaultProps} features={validFeatures} />);
|
|
291
|
+
|
|
292
|
+
expect(mockLogger.warn).not.toHaveBeenCalled();
|
|
137
293
|
});
|
|
138
294
|
});
|
|
139
295
|
|
|
140
296
|
describe('Event Handlers', () => {
|
|
141
|
-
it('
|
|
297
|
+
it('passes onEditRow handler to DataTableCore', () => {
|
|
142
298
|
const onEditRow = vi.fn();
|
|
143
299
|
render(<DataTable {...defaultProps} onEditRow={onEditRow} />);
|
|
144
300
|
|
|
145
|
-
expect(
|
|
301
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
302
|
+
expect.objectContaining({
|
|
303
|
+
onEditRow,
|
|
304
|
+
}),
|
|
305
|
+
expect.anything()
|
|
306
|
+
);
|
|
146
307
|
});
|
|
147
308
|
|
|
148
|
-
it('
|
|
309
|
+
it('passes onDeleteRow handler to DataTableCore', () => {
|
|
149
310
|
const onDeleteRow = vi.fn();
|
|
150
311
|
render(<DataTable {...defaultProps} onDeleteRow={onDeleteRow} />);
|
|
151
312
|
|
|
152
|
-
expect(
|
|
313
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
314
|
+
expect.objectContaining({
|
|
315
|
+
onDeleteRow,
|
|
316
|
+
}),
|
|
317
|
+
expect.anything()
|
|
318
|
+
);
|
|
153
319
|
});
|
|
154
320
|
|
|
155
|
-
it('
|
|
321
|
+
it('passes onCreateRow handler to DataTableCore', () => {
|
|
156
322
|
const onCreateRow = vi.fn();
|
|
157
323
|
render(<DataTable {...defaultProps} onCreateRow={onCreateRow} />);
|
|
158
324
|
|
|
159
|
-
expect(
|
|
325
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
326
|
+
expect.objectContaining({
|
|
327
|
+
onCreateRow,
|
|
328
|
+
}),
|
|
329
|
+
expect.anything()
|
|
330
|
+
);
|
|
160
331
|
});
|
|
161
332
|
|
|
162
|
-
it('
|
|
333
|
+
it('passes onImport handler to DataTableCore', () => {
|
|
163
334
|
const onImport = vi.fn();
|
|
164
335
|
render(<DataTable {...defaultProps} onImport={onImport} />);
|
|
165
336
|
|
|
166
|
-
expect(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
337
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
338
|
+
expect.objectContaining({
|
|
339
|
+
onImport,
|
|
340
|
+
}),
|
|
341
|
+
expect.anything()
|
|
342
|
+
);
|
|
174
343
|
});
|
|
175
344
|
|
|
176
|
-
it('
|
|
345
|
+
it('passes onRowSelectionChange handler to DataTableCore', () => {
|
|
177
346
|
const onRowSelectionChange = vi.fn();
|
|
178
347
|
render(<DataTable {...defaultProps} onRowSelectionChange={onRowSelectionChange} />);
|
|
179
348
|
|
|
180
|
-
expect(
|
|
349
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
350
|
+
expect.objectContaining({
|
|
351
|
+
onRowSelectionChange,
|
|
352
|
+
}),
|
|
353
|
+
expect.anything()
|
|
354
|
+
);
|
|
181
355
|
});
|
|
182
356
|
|
|
183
|
-
it('
|
|
357
|
+
it('passes onDeleteSelected handler to DataTableCore', () => {
|
|
184
358
|
const onDeleteSelected = vi.fn();
|
|
185
359
|
render(<DataTable {...defaultProps} onDeleteSelected={onDeleteSelected} />);
|
|
186
360
|
|
|
187
|
-
expect(
|
|
361
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
362
|
+
expect.objectContaining({
|
|
363
|
+
onDeleteSelected,
|
|
364
|
+
}),
|
|
365
|
+
expect.anything()
|
|
366
|
+
);
|
|
188
367
|
});
|
|
189
368
|
});
|
|
190
369
|
|
|
191
|
-
describe('
|
|
192
|
-
it('
|
|
193
|
-
|
|
194
|
-
serverSide: true,
|
|
195
|
-
serverSidePagination: true,
|
|
196
|
-
serverSideSorting: true,
|
|
197
|
-
};
|
|
198
|
-
render(<DataTable {...defaultProps} {...serverConfig} />);
|
|
370
|
+
describe('Data Handling', () => {
|
|
371
|
+
it('handles empty data array', () => {
|
|
372
|
+
render(<DataTable {...defaultProps} data={[]} />);
|
|
199
373
|
|
|
200
374
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
375
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
376
|
+
expect.stringContaining('[DataTable]'),
|
|
377
|
+
expect.objectContaining({
|
|
378
|
+
dataLength: 0,
|
|
379
|
+
})
|
|
380
|
+
);
|
|
201
381
|
});
|
|
202
382
|
|
|
203
|
-
it('
|
|
204
|
-
|
|
383
|
+
it('handles single data item', () => {
|
|
384
|
+
const singleData = testDataScenarios.single;
|
|
385
|
+
render(<DataTable {...defaultProps} data={singleData} />);
|
|
386
|
+
|
|
387
|
+
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
388
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
389
|
+
expect.stringContaining('[DataTable]'),
|
|
390
|
+
expect.objectContaining({
|
|
391
|
+
dataLength: 1,
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('handles large dataset', () => {
|
|
397
|
+
const largeData = testDataScenarios.large;
|
|
398
|
+
render(<DataTable {...defaultProps} data={largeData} />);
|
|
399
|
+
|
|
400
|
+
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
401
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
402
|
+
expect.stringContaining('[DataTable]'),
|
|
403
|
+
expect.objectContaining({
|
|
404
|
+
dataLength: largeData.length,
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('handles data with null values', () => {
|
|
410
|
+
const dataWithNulls = testDataScenarios.withNulls;
|
|
411
|
+
render(<DataTable {...defaultProps} data={dataWithNulls} />);
|
|
205
412
|
|
|
206
413
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
207
414
|
});
|
|
415
|
+
});
|
|
208
416
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
417
|
+
describe('Performance Configuration', () => {
|
|
418
|
+
it('passes performance config to DataTableCore', () => {
|
|
419
|
+
const performance = {
|
|
420
|
+
virtualScrolling: true,
|
|
212
421
|
enableChunking: true,
|
|
213
422
|
};
|
|
214
|
-
render(<DataTable {...defaultProps} {...chunkingConfig} />);
|
|
215
423
|
|
|
216
|
-
|
|
424
|
+
render(<DataTable {...defaultProps} performance={performance} />);
|
|
425
|
+
|
|
426
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
427
|
+
expect.objectContaining({
|
|
428
|
+
performance,
|
|
429
|
+
}),
|
|
430
|
+
expect.anything()
|
|
431
|
+
);
|
|
217
432
|
});
|
|
218
433
|
|
|
219
|
-
it('
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
434
|
+
it('passes serverSide config to DataTableCore', () => {
|
|
435
|
+
const serverSide = {
|
|
436
|
+
fetchData: vi.fn(),
|
|
437
|
+
enableServerSorting: true,
|
|
223
438
|
};
|
|
224
|
-
render(<DataTable {...defaultProps} {...searchConfig} />);
|
|
225
439
|
|
|
226
|
-
|
|
440
|
+
render(<DataTable {...defaultProps} serverSide={serverSide} />);
|
|
441
|
+
|
|
442
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
443
|
+
expect.objectContaining({
|
|
444
|
+
serverSide,
|
|
445
|
+
}),
|
|
446
|
+
expect.anything()
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('passes paginationMode to DataTableCore', () => {
|
|
451
|
+
render(<DataTable {...defaultProps} paginationMode="server" />);
|
|
452
|
+
|
|
453
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledWith(
|
|
454
|
+
expect.objectContaining({
|
|
455
|
+
paginationMode: 'server',
|
|
456
|
+
}),
|
|
457
|
+
expect.anything()
|
|
458
|
+
);
|
|
227
459
|
});
|
|
228
460
|
});
|
|
229
461
|
|
|
230
462
|
describe('Component Integration', () => {
|
|
231
|
-
it('passes all props to DataTableCore', () => {
|
|
463
|
+
it('passes all props to DataTableCore except features', () => {
|
|
232
464
|
const allProps = {
|
|
233
465
|
...defaultProps,
|
|
234
466
|
title: 'Test Table',
|
|
235
467
|
description: 'Test description',
|
|
236
|
-
variant: 'compact',
|
|
237
|
-
|
|
468
|
+
variant: 'compact' as const,
|
|
469
|
+
className: 'custom-class',
|
|
470
|
+
features: createFullFeatures(),
|
|
238
471
|
onEditRow: vi.fn(),
|
|
239
472
|
onDeleteRow: vi.fn(),
|
|
240
473
|
onCreateRow: vi.fn(),
|
|
241
474
|
onImport: vi.fn(),
|
|
242
|
-
onExport: vi.fn(),
|
|
243
475
|
onRowSelectionChange: vi.fn(),
|
|
244
476
|
onDeleteSelected: vi.fn(),
|
|
477
|
+
performance: { virtualScrolling: true },
|
|
478
|
+
initialPageSize: 25,
|
|
245
479
|
};
|
|
480
|
+
|
|
246
481
|
render(<DataTable {...allProps} />);
|
|
247
482
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
it('handles React.memo optimization', () => {
|
|
252
|
-
const { rerender } = render(<DataTable {...defaultProps} />);
|
|
483
|
+
const mockDataTableCore = getMockedDataTableCore();
|
|
484
|
+
const lastCall = mockDataTableCore.mock.calls[mockDataTableCore.mock.calls.length - 1][0];
|
|
253
485
|
|
|
254
|
-
expect(
|
|
486
|
+
expect(lastCall).toMatchObject({
|
|
487
|
+
data: allProps.data,
|
|
488
|
+
columns: allProps.columns,
|
|
489
|
+
rbac: allProps.rbac,
|
|
490
|
+
title: allProps.title,
|
|
491
|
+
description: allProps.description,
|
|
492
|
+
variant: allProps.variant,
|
|
493
|
+
className: allProps.className,
|
|
494
|
+
onEditRow: allProps.onEditRow,
|
|
495
|
+
onDeleteRow: allProps.onDeleteRow,
|
|
496
|
+
onCreateRow: allProps.onCreateRow,
|
|
497
|
+
onImport: allProps.onImport,
|
|
498
|
+
onRowSelectionChange: allProps.onRowSelectionChange,
|
|
499
|
+
onDeleteSelected: allProps.onDeleteSelected,
|
|
500
|
+
performance: allProps.performance,
|
|
501
|
+
initialPageSize: allProps.initialPageSize,
|
|
502
|
+
});
|
|
255
503
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
expect(
|
|
504
|
+
// Features should be normalized, not the original object
|
|
505
|
+
expect(lastCall.features).toBeDefined();
|
|
506
|
+
expect(lastCall.features).not.toBe(allProps.features);
|
|
259
507
|
});
|
|
260
508
|
});
|
|
261
509
|
|
|
262
|
-
describe('
|
|
263
|
-
it('
|
|
264
|
-
render(<DataTable {...defaultProps} />);
|
|
510
|
+
describe('Memory Management', () => {
|
|
511
|
+
it('cleans up resources on unmount', () => {
|
|
512
|
+
const { unmount } = render(<DataTable {...defaultProps} />);
|
|
265
513
|
|
|
266
514
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
describe('Accessibility', () => {
|
|
271
|
-
it('renders with proper ARIA roles', () => {
|
|
272
|
-
render(<DataTable {...defaultProps} />);
|
|
273
515
|
|
|
274
|
-
|
|
516
|
+
unmount();
|
|
517
|
+
expect(screen.queryByTestId('data-table-core')).not.toBeInTheDocument();
|
|
275
518
|
});
|
|
276
519
|
|
|
277
|
-
it('
|
|
278
|
-
render(<DataTable {...defaultProps}
|
|
520
|
+
it('handles rapid mount/unmount cycles', () => {
|
|
521
|
+
const { unmount: unmount1 } = render(<DataTable {...defaultProps} />);
|
|
522
|
+
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
523
|
+
unmount1();
|
|
279
524
|
|
|
525
|
+
const { unmount: unmount2 } = render(<DataTable {...defaultProps} />);
|
|
280
526
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
527
|
+
unmount2();
|
|
528
|
+
|
|
529
|
+
expect(screen.queryByTestId('data-table-core')).not.toBeInTheDocument();
|
|
281
530
|
});
|
|
531
|
+
});
|
|
282
532
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
pagination: true,
|
|
287
|
-
sorting: true,
|
|
288
|
-
editing: true,
|
|
289
|
-
deletion: true,
|
|
290
|
-
import: true,
|
|
291
|
-
export: true,
|
|
292
|
-
};
|
|
293
|
-
render(<DataTable {...defaultProps} features={features} />);
|
|
533
|
+
describe('Edge Cases', () => {
|
|
534
|
+
it('handles missing columns gracefully', () => {
|
|
535
|
+
render(<DataTable {...defaultProps} columns={[]} />);
|
|
294
536
|
|
|
295
537
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
538
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
539
|
+
expect.stringContaining('[DataTable]'),
|
|
540
|
+
expect.objectContaining({
|
|
541
|
+
columnsCount: 0,
|
|
542
|
+
})
|
|
543
|
+
);
|
|
296
544
|
});
|
|
297
|
-
});
|
|
298
545
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const { unmount } = render(<DataTable {...defaultProps} />);
|
|
546
|
+
it('handles undefined rbac pageId and pageName', () => {
|
|
547
|
+
render(<DataTable {...defaultProps} rbac={{}} />);
|
|
302
548
|
|
|
303
549
|
expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
550
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
551
|
+
expect.stringContaining('[DataTable]'),
|
|
552
|
+
expect.objectContaining({
|
|
553
|
+
pageName: undefined,
|
|
554
|
+
})
|
|
555
|
+
);
|
|
308
556
|
});
|
|
309
557
|
|
|
310
|
-
it('handles rapid
|
|
311
|
-
const {
|
|
558
|
+
it('handles rapid feature prop changes', () => {
|
|
559
|
+
const { rerender } = render(<DataTable {...defaultProps} features={{ search: true }} />);
|
|
312
560
|
|
|
313
|
-
expect(
|
|
561
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(1);
|
|
314
562
|
|
|
315
|
-
|
|
316
|
-
expect(
|
|
563
|
+
rerender(<DataTable {...defaultProps} features={{ search: false }} />);
|
|
564
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(2);
|
|
565
|
+
|
|
566
|
+
rerender(<DataTable {...defaultProps} features={{ search: true, pagination: true }} />);
|
|
567
|
+
expect(getMockedDataTableCore()).toHaveBeenCalledTimes(3);
|
|
317
568
|
});
|
|
318
569
|
});
|
|
319
570
|
});
|