@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.
Files changed (155) hide show
  1. package/dist/{DataTable-5W2HVLLV.js → DataTable-3D3BUZDV.js} +8 -8
  2. package/dist/{UnifiedAuthProvider-LUM3QLS5.js → UnifiedAuthProvider-KZZUO27W.js} +3 -3
  3. package/dist/{api-SIZPFBFX.js → api-QPMBZZUZ.js} +3 -3
  4. package/dist/{audit-5JI5T3SL.js → audit-H4YJJF7R.js} +2 -2
  5. package/dist/{chunk-IWJYNWXN.js → chunk-3OGQLOJM.js} +11 -3
  6. package/dist/chunk-3OGQLOJM.js.map +1 -0
  7. package/dist/{chunk-TDFBX7KJ.js → chunk-7H75SHXZ.js} +2 -2
  8. package/dist/{chunk-EFVQBYFN.js → chunk-BUN7NMV7.js} +2 -2
  9. package/dist/{chunk-ACYQNYHB.js → chunk-C5RN4TE5.js} +7 -7
  10. package/dist/{chunk-2BIDKXQU.js → chunk-EKVVTPIF.js} +82 -23
  11. package/dist/chunk-EKVVTPIF.js.map +1 -0
  12. package/dist/{chunk-X7SPKHYZ.js → chunk-F6QB26OS.js} +4 -4
  13. package/dist/{chunk-UGVU7L7N.js → chunk-I7JC7PTJ.js} +6 -6
  14. package/dist/chunk-I7JC7PTJ.js.map +1 -0
  15. package/dist/{chunk-I5YM5BGS.js → chunk-L36JW4KV.js} +2 -2
  16. package/dist/{chunk-ZL45MG76.js → chunk-MNSGWRPB.js} +15 -15
  17. package/dist/{chunk-JE2GFA3O.js → chunk-NEONKMTU.js} +3 -3
  18. package/dist/{chunk-MW73E7SP.js → chunk-OO3V7W4H.js} +2 -2
  19. package/dist/chunk-OO3V7W4H.js.map +1 -0
  20. package/dist/{chunk-PXXS26G5.js → chunk-TAJRS6YB.js} +2 -2
  21. package/dist/{chunk-TD4BXGPE.js → chunk-WMPZY26G.js} +8 -4
  22. package/dist/{chunk-TD4BXGPE.js.map → chunk-WMPZY26G.js.map} +1 -1
  23. package/dist/components.js +10 -10
  24. package/dist/hooks.js +7 -7
  25. package/dist/index.js +13 -13
  26. package/dist/providers.js +2 -2
  27. package/dist/rbac/index.js +9 -9
  28. package/dist/utils.js +1 -1
  29. package/docs/api/classes/ColumnFactory.md +1 -1
  30. package/docs/api/classes/ErrorBoundary.md +1 -1
  31. package/docs/api/classes/InvalidScopeError.md +1 -1
  32. package/docs/api/classes/MissingUserContextError.md +1 -1
  33. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  34. package/docs/api/classes/PermissionDeniedError.md +1 -1
  35. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  36. package/docs/api/classes/RBACAuditManager.md +8 -8
  37. package/docs/api/classes/RBACCache.md +1 -1
  38. package/docs/api/classes/RBACEngine.md +1 -1
  39. package/docs/api/classes/RBACError.md +1 -1
  40. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  41. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  42. package/docs/api/classes/StorageUtils.md +1 -1
  43. package/docs/api/enums/FileCategory.md +1 -1
  44. package/docs/api/interfaces/AggregateConfig.md +1 -1
  45. package/docs/api/interfaces/ButtonProps.md +1 -1
  46. package/docs/api/interfaces/CardProps.md +1 -1
  47. package/docs/api/interfaces/ColorPalette.md +1 -1
  48. package/docs/api/interfaces/ColorShade.md +1 -1
  49. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  50. package/docs/api/interfaces/DataRecord.md +1 -1
  51. package/docs/api/interfaces/DataTableAction.md +1 -1
  52. package/docs/api/interfaces/DataTableColumn.md +1 -1
  53. package/docs/api/interfaces/DataTableProps.md +1 -1
  54. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  55. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  56. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  57. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  58. package/docs/api/interfaces/FileMetadata.md +1 -1
  59. package/docs/api/interfaces/FileReference.md +1 -1
  60. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  61. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  62. package/docs/api/interfaces/FileUploadProps.md +1 -1
  63. package/docs/api/interfaces/FooterProps.md +1 -1
  64. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  65. package/docs/api/interfaces/InputProps.md +1 -1
  66. package/docs/api/interfaces/LabelProps.md +1 -1
  67. package/docs/api/interfaces/LoginFormProps.md +1 -1
  68. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  69. package/docs/api/interfaces/NavigationContextType.md +1 -1
  70. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  71. package/docs/api/interfaces/NavigationItem.md +1 -1
  72. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  73. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  74. package/docs/api/interfaces/Organisation.md +1 -1
  75. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  76. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  77. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  78. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  79. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  80. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  81. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  82. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  83. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  84. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  85. package/docs/api/interfaces/PaletteData.md +1 -1
  86. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  87. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  88. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  89. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  90. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  91. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  92. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  94. package/docs/api/interfaces/RBACConfig.md +1 -1
  95. package/docs/api/interfaces/RBACLogger.md +1 -1
  96. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  97. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  98. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  99. package/docs/api/interfaces/RouteConfig.md +1 -1
  100. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  101. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  102. package/docs/api/interfaces/StorageConfig.md +1 -1
  103. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  104. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  105. package/docs/api/interfaces/StorageListOptions.md +1 -1
  106. package/docs/api/interfaces/StorageListResult.md +1 -1
  107. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  108. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  109. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  110. package/docs/api/interfaces/StyleImport.md +1 -1
  111. package/docs/api/interfaces/SwitchProps.md +1 -1
  112. package/docs/api/interfaces/ToastActionElement.md +1 -1
  113. package/docs/api/interfaces/ToastProps.md +1 -1
  114. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  115. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  116. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  117. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  118. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  119. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  120. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  121. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  123. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  124. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  125. package/docs/api/interfaces/UserEventAccess.md +1 -1
  126. package/docs/api/interfaces/UserMenuProps.md +1 -1
  127. package/docs/api/interfaces/UserProfile.md +1 -1
  128. package/docs/api/modules.md +12 -12
  129. package/package.json +1 -1
  130. package/src/components/DataTable/DataTable.test.tsx +405 -154
  131. package/src/components/DataTable/components/DataTableCore.tsx +6 -1
  132. package/src/components/EventSelector/EventSelector.tsx +32 -2
  133. package/src/components/NavigationMenu/NavigationMenu.test.tsx +56 -8
  134. package/src/components/NavigationMenu/NavigationMenu.tsx +75 -12
  135. package/src/rbac/audit-enhanced.ts +14 -2
  136. package/src/rbac/audit.test.ts +16 -6
  137. package/src/rbac/audit.ts +11 -1
  138. package/src/rbac/hooks/usePermissions.ts +18 -2
  139. package/src/services/EventService.ts +3 -2
  140. package/dist/chunk-2BIDKXQU.js.map +0 -1
  141. package/dist/chunk-IWJYNWXN.js.map +0 -1
  142. package/dist/chunk-MW73E7SP.js.map +0 -1
  143. package/dist/chunk-UGVU7L7N.js.map +0 -1
  144. /package/dist/{DataTable-5W2HVLLV.js.map → DataTable-3D3BUZDV.js.map} +0 -0
  145. /package/dist/{UnifiedAuthProvider-LUM3QLS5.js.map → UnifiedAuthProvider-KZZUO27W.js.map} +0 -0
  146. /package/dist/{api-SIZPFBFX.js.map → api-QPMBZZUZ.js.map} +0 -0
  147. /package/dist/{audit-5JI5T3SL.js.map → audit-H4YJJF7R.js.map} +0 -0
  148. /package/dist/{chunk-TDFBX7KJ.js.map → chunk-7H75SHXZ.js.map} +0 -0
  149. /package/dist/{chunk-EFVQBYFN.js.map → chunk-BUN7NMV7.js.map} +0 -0
  150. /package/dist/{chunk-ACYQNYHB.js.map → chunk-C5RN4TE5.js.map} +0 -0
  151. /package/dist/{chunk-X7SPKHYZ.js.map → chunk-F6QB26OS.js.map} +0 -0
  152. /package/dist/{chunk-I5YM5BGS.js.map → chunk-L36JW4KV.js.map} +0 -0
  153. /package/dist/{chunk-ZL45MG76.js.map → chunk-MNSGWRPB.js.map} +0 -0
  154. /package/dist/{chunk-JE2GFA3O.js.map → chunk-NEONKMTU.js.map} +0 -0
  155. /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
- // Mock DataTableCore to avoid complex dependencies
9
- vi.mock('./components/DataTableCore', () => ({
10
- DataTableCore: ({ features, ...props }: any) => (
11
- <div data-testid="data-table-core" data-features={JSON.stringify(features)} {...props}>
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
- vi.mock('../../utils/logger', () => ({
19
- createLogger: () => ({
20
- info: vi.fn(),
21
- warn: vi.fn(),
22
- error: vi.fn(),
23
- }),
24
- }));
25
-
26
- // Mock the feature normalization utility
27
- vi.mock('./utils/normalizeDataTableFeatures', () => ({
28
- normalizeDataTableFeatures: (features: any) => features || {},
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('Basic Rendering', () => {
52
- it('renders DataTableCore with basic props', () => {
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('renders with default features', () => {
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
- expect(core).toBeInTheDocument();
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('renders with custom features', () => {
86
- const features = createDefaultFeatures();
87
- render(<DataTable {...defaultProps} features={features} />);
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
- expect(core).toBeInTheDocument();
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('renders with enabled features', () => {
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
- render(<DataTable {...defaultProps} features={features} />);
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
- const core = screen.getByTestId('data-table-core');
102
- expect(core).toBeInTheDocument();
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('renders with read permissions', () => {
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(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('renders with write permissions', () => {
114
- const propsWithWrite = {
115
- ...defaultProps,
116
- rbac: {
117
- permissions: ['read', 'write'],
118
- accessLevel: 'write' as const,
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('renders with admin permissions', () => {
127
- const propsWithAdmin = {
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(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('renders with edit row handler', () => {
297
+ it('passes onEditRow handler to DataTableCore', () => {
142
298
  const onEditRow = vi.fn();
143
299
  render(<DataTable {...defaultProps} onEditRow={onEditRow} />);
144
300
 
145
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
301
+ expect(getMockedDataTableCore()).toHaveBeenCalledWith(
302
+ expect.objectContaining({
303
+ onEditRow,
304
+ }),
305
+ expect.anything()
306
+ );
146
307
  });
147
308
 
148
- it('renders with delete row handler', () => {
309
+ it('passes onDeleteRow handler to DataTableCore', () => {
149
310
  const onDeleteRow = vi.fn();
150
311
  render(<DataTable {...defaultProps} onDeleteRow={onDeleteRow} />);
151
312
 
152
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
313
+ expect(getMockedDataTableCore()).toHaveBeenCalledWith(
314
+ expect.objectContaining({
315
+ onDeleteRow,
316
+ }),
317
+ expect.anything()
318
+ );
153
319
  });
154
320
 
155
- it('renders with create row handler', () => {
321
+ it('passes onCreateRow handler to DataTableCore', () => {
156
322
  const onCreateRow = vi.fn();
157
323
  render(<DataTable {...defaultProps} onCreateRow={onCreateRow} />);
158
324
 
159
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
325
+ expect(getMockedDataTableCore()).toHaveBeenCalledWith(
326
+ expect.objectContaining({
327
+ onCreateRow,
328
+ }),
329
+ expect.anything()
330
+ );
160
331
  });
161
332
 
162
- it('renders with import handler', () => {
333
+ it('passes onImport handler to DataTableCore', () => {
163
334
  const onImport = vi.fn();
164
335
  render(<DataTable {...defaultProps} onImport={onImport} />);
165
336
 
166
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
167
- });
168
-
169
- it('renders with export handler', () => {
170
- const onExport = vi.fn();
171
- render(<DataTable {...defaultProps} onExport={onExport} />);
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('renders with row selection change handler', () => {
345
+ it('passes onRowSelectionChange handler to DataTableCore', () => {
177
346
  const onRowSelectionChange = vi.fn();
178
347
  render(<DataTable {...defaultProps} onRowSelectionChange={onRowSelectionChange} />);
179
348
 
180
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
349
+ expect(getMockedDataTableCore()).toHaveBeenCalledWith(
350
+ expect.objectContaining({
351
+ onRowSelectionChange,
352
+ }),
353
+ expect.anything()
354
+ );
181
355
  });
182
356
 
183
- it('renders with delete selected handler', () => {
357
+ it('passes onDeleteSelected handler to DataTableCore', () => {
184
358
  const onDeleteSelected = vi.fn();
185
359
  render(<DataTable {...defaultProps} onDeleteSelected={onDeleteSelected} />);
186
360
 
187
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
361
+ expect(getMockedDataTableCore()).toHaveBeenCalledWith(
362
+ expect.objectContaining({
363
+ onDeleteSelected,
364
+ }),
365
+ expect.anything()
366
+ );
188
367
  });
189
368
  });
190
369
 
191
- describe('Performance Configuration', () => {
192
- it('renders with server-side configuration', () => {
193
- const serverConfig = {
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('renders with pagination mode override', () => {
204
- render(<DataTable {...defaultProps} paginationMode="server" />);
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
- it('renders with chunking configuration', () => {
210
- const chunkingConfig = {
211
- chunkSize: 100,
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('renders with search index configuration', () => {
220
- const searchConfig = {
221
- searchIndex: ['name', 'email'],
222
- enableSearchIndex: true,
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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
- features: createDefaultFeatures(),
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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(screen.getByTestId('data-table-core')).toBeInTheDocument();
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
- // Re-render with same props should not cause issues
257
- rerender(<DataTable {...defaultProps} />);
258
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
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('Error Boundaries', () => {
263
- it('handles component errors gracefully', () => {
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
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
516
+ unmount();
517
+ expect(screen.queryByTestId('data-table-core')).not.toBeInTheDocument();
275
518
  });
276
519
 
277
- it('maintains accessibility with custom variants', () => {
278
- render(<DataTable {...defaultProps} variant="compact" />);
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
- it('preserves accessibility with all features enabled', () => {
284
- const features = {
285
- search: true,
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
- describe('Memory Management', () => {
300
- it('cleans up resources on unmount', () => {
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
- unmount();
306
- // Component should be unmounted
307
- expect(screen.queryByTestId('data-table-core')).not.toBeInTheDocument();
550
+ expect(consoleLogSpy).toHaveBeenCalledWith(
551
+ expect.stringContaining('[DataTable]'),
552
+ expect.objectContaining({
553
+ pageName: undefined,
554
+ })
555
+ );
308
556
  });
309
557
 
310
- it('handles rapid mount/unmount cycles', () => {
311
- const { unmount } = render(<DataTable {...defaultProps} />);
558
+ it('handles rapid feature prop changes', () => {
559
+ const { rerender } = render(<DataTable {...defaultProps} features={{ search: true }} />);
312
560
 
313
- expect(screen.getByTestId('data-table-core')).toBeInTheDocument();
561
+ expect(getMockedDataTableCore()).toHaveBeenCalledTimes(1);
314
562
 
315
- unmount();
316
- expect(screen.queryByTestId('data-table-core')).not.toBeInTheDocument();
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
  });