@jmruthers/pace-core 0.5.118 → 0.5.120

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 (181) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-DGZDJUYM.js} +7 -7
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
  4. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  5. package/dist/chunk-BHWIUEYH.js.map +1 -0
  6. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  7. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  8. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  9. package/dist/{chunk-2GJ5GL77.js → chunk-GKHF54DI.js} +2 -2
  10. package/dist/chunk-GKHF54DI.js.map +1 -0
  11. package/dist/{chunk-UKZWNQMB.js → chunk-HFBOFZ3Z.js} +5 -18
  12. package/dist/chunk-HFBOFZ3Z.js.map +1 -0
  13. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  14. package/dist/{chunk-2LM4QQGH.js → chunk-QPI2CCBA.js} +9 -9
  15. package/dist/chunk-QPI2CCBA.js.map +1 -0
  16. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  17. package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
  18. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  19. package/dist/components.d.ts +1 -1
  20. package/dist/components.js +9 -9
  21. package/dist/hooks.d.ts +1 -1
  22. package/dist/hooks.js +8 -8
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +12 -12
  25. package/dist/providers.js +2 -2
  26. package/dist/rbac/index.js +7 -7
  27. package/dist/{useToast-Cs_g32bg.d.ts → useToast-C8gR5ir4.d.ts} +2 -2
  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 +1 -1
  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/EventAppRoleData.md +1 -1
  58. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  59. package/docs/api/interfaces/FileMetadata.md +1 -1
  60. package/docs/api/interfaces/FileReference.md +1 -1
  61. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  62. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  63. package/docs/api/interfaces/FileUploadProps.md +1 -1
  64. package/docs/api/interfaces/FooterProps.md +1 -1
  65. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  66. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  67. package/docs/api/interfaces/InputProps.md +1 -1
  68. package/docs/api/interfaces/LabelProps.md +1 -1
  69. package/docs/api/interfaces/LoginFormProps.md +1 -1
  70. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  71. package/docs/api/interfaces/NavigationContextType.md +1 -1
  72. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  73. package/docs/api/interfaces/NavigationItem.md +1 -1
  74. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  76. package/docs/api/interfaces/Organisation.md +1 -1
  77. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  78. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  79. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  80. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  81. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  82. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  83. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  84. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  85. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  86. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  87. package/docs/api/interfaces/PaletteData.md +1 -1
  88. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  89. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  90. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  92. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  96. package/docs/api/interfaces/RBACConfig.md +1 -1
  97. package/docs/api/interfaces/RBACLogger.md +1 -1
  98. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  99. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  100. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  101. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  102. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  103. package/docs/api/interfaces/RouteConfig.md +1 -1
  104. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  105. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  106. package/docs/api/interfaces/StorageConfig.md +1 -1
  107. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  108. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  109. package/docs/api/interfaces/StorageListOptions.md +1 -1
  110. package/docs/api/interfaces/StorageListResult.md +1 -1
  111. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  112. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  113. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  114. package/docs/api/interfaces/StyleImport.md +1 -1
  115. package/docs/api/interfaces/SwitchProps.md +1 -1
  116. package/docs/api/interfaces/ToastActionElement.md +1 -1
  117. package/docs/api/interfaces/ToastProps.md +1 -1
  118. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  119. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  120. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  121. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  124. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  125. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  127. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  128. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  129. package/docs/api/interfaces/UserEventAccess.md +1 -1
  130. package/docs/api/interfaces/UserMenuProps.md +1 -1
  131. package/docs/api/interfaces/UserProfile.md +1 -1
  132. package/docs/api/modules.md +2 -2
  133. package/package.json +1 -1
  134. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  135. package/src/components/DataTable/components/DataTableCore.tsx +5 -0
  136. package/src/components/DataTable/components/EditableRow.tsx +9 -18
  137. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +616 -9
  138. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  139. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  140. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  141. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  142. package/src/components/Toast/Toast.tsx +1 -1
  143. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  144. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  145. package/src/hooks/__tests__/useEvents.unit.test.ts +251 -0
  146. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  147. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  148. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +19 -9
  149. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  150. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  151. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  152. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  153. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +661 -0
  154. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  155. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +371 -0
  156. package/src/hooks/__tests__/useToast.unit.test.tsx +449 -30
  157. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  158. package/src/hooks/useToast.ts +4 -4
  159. package/src/rbac/audit-enhanced.ts +339 -0
  160. package/src/services/EventService.ts +1 -0
  161. package/src/services/__tests__/AuthService.test.ts +473 -0
  162. package/src/services/__tests__/EventService.test.ts +390 -0
  163. package/src/services/__tests__/InactivityService.test.ts +217 -0
  164. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  165. package/src/styles/core.css +1 -0
  166. package/dist/chunk-2GJ5GL77.js.map +0 -1
  167. package/dist/chunk-2LM4QQGH.js.map +0 -1
  168. package/dist/chunk-KA3PSVNV.js.map +0 -1
  169. package/dist/chunk-UKZWNQMB.js.map +0 -1
  170. package/src/components/DataTable/utils/debugTools.ts +0 -609
  171. package/src/rbac/testing/index.tsx +0 -340
  172. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-DGZDJUYM.js.map} +0 -0
  173. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  174. /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
  175. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  176. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  177. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  178. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  179. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  180. /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  181. /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
@@ -0,0 +1,748 @@
1
+ /**
2
+ * @file useDataTablePerformance Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__
5
+ * @since 0.3.0
6
+ *
7
+ * Comprehensive tests for the useDataTablePerformance hook following TEST_STANDARD.md.
8
+ * Tests focus on pagination, virtualization, search, server-side fetching, error recovery, and memory monitoring.
9
+ */
10
+
11
+ import { renderHook, waitFor, act } from '@testing-library/react';
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
+ import { useDataTablePerformance } from '../useDataTablePerformance';
14
+ import type { DataRecord, ServerSideConfig, ServerSideParams, ServerSideResponse } from '../../components/DataTable/types';
15
+
16
+ // Mock performance utilities
17
+ vi.mock('../../components/DataTable/utils/performanceUtils', () => ({
18
+ determinePaginationMode: vi.fn((length: number) => {
19
+ if (length < 1000) return 'client';
20
+ return 'hybrid';
21
+ }),
22
+ getOptimalPageSizeOptions: vi.fn((mode: string, length: number) => {
23
+ if (mode === 'client') return [10, 25, 50, 100];
24
+ if (mode === 'hybrid') return [25, 50, 100, 200];
25
+ return [10, 25, 50];
26
+ }),
27
+ DataChunkManager: class {
28
+ clear = vi.fn();
29
+ getMemoryUsage = vi.fn(() => 0.5);
30
+ constructor() {
31
+ // Constructor
32
+ }
33
+ } as any,
34
+ SearchIndex: class MockSearchIndex {
35
+ buildIndex: ReturnType<typeof vi.fn>;
36
+ search: ReturnType<typeof vi.fn>;
37
+ constructor(config?: any) {
38
+ this.buildIndex = vi.fn();
39
+ this.search = vi.fn(() => [0, 1, 2]);
40
+ }
41
+ } as any,
42
+ debounce: vi.fn((fn: Function, delay: number) => fn),
43
+ VisibilityTracker: class {
44
+ destroy = vi.fn();
45
+ onVisibilityChange = vi.fn(() => vi.fn()); // Return unsubscribe function
46
+ constructor() {
47
+ // Constructor
48
+ }
49
+ } as any
50
+ }));
51
+
52
+ // Mock error handling utilities
53
+ vi.mock('../../components/DataTable/utils/errorHandling', () => ({
54
+ ErrorRecoveryManager: class {
55
+ handleError = vi.fn().mockResolvedValue({ recovered: true });
56
+ clearErrorLog = vi.fn();
57
+ constructor() {
58
+ // Constructor
59
+ }
60
+ } as any,
61
+ MemoryMonitor: class {
62
+ stopMonitoring = vi.fn();
63
+ startMonitoring = vi.fn();
64
+ getMemoryUsage = vi.fn(() => 0);
65
+ constructor() {
66
+ // Constructor
67
+ }
68
+ } as any,
69
+ CircuitBreaker: vi.fn().mockImplementation(() => ({
70
+ isOpen: false,
71
+ recordSuccess: vi.fn(),
72
+ recordFailure: vi.fn()
73
+ })),
74
+ DEFAULT_FALLBACK_CONFIG: {
75
+ enableFallbacks: true,
76
+ maxRetries: 3
77
+ },
78
+ safeExecute: vi.fn((fn: Function) => Promise.resolve(fn()))
79
+ }));
80
+
81
+ import { determinePaginationMode, getOptimalPageSizeOptions } from '../../components/DataTable/utils/performanceUtils';
82
+ import { ErrorRecoveryManager } from '../../components/DataTable/utils/errorHandling';
83
+
84
+ describe('useDataTablePerformance Hook', () => {
85
+ interface TestRecord extends DataRecord {
86
+ id: string;
87
+ name: string;
88
+ value: number;
89
+ }
90
+
91
+ const mockData: TestRecord[] = [
92
+ { id: '1', name: 'Test 1', value: 10 },
93
+ { id: '2', name: 'Test 2', value: 20 },
94
+ { id: '3', name: 'Test 3', value: 30 }
95
+ ];
96
+
97
+ beforeEach(() => {
98
+ vi.clearAllMocks();
99
+ });
100
+
101
+ afterEach(() => {
102
+ vi.clearAllMocks();
103
+ });
104
+
105
+ describe('Pagination Mode Determination', () => {
106
+ it('returns client mode for small datasets', () => {
107
+ const { result } = renderHook(() =>
108
+ useDataTablePerformance({
109
+ data: mockData
110
+ })
111
+ );
112
+
113
+ expect(result.current.paginationMode).toBe('client');
114
+ expect(determinePaginationMode).toHaveBeenCalledWith(3, undefined);
115
+ });
116
+
117
+ it('returns server mode when serverSide config is provided', () => {
118
+ const serverSide: ServerSideConfig<TestRecord> = {
119
+ fetchData: vi.fn()
120
+ };
121
+
122
+ const { result } = renderHook(() =>
123
+ useDataTablePerformance({
124
+ data: mockData,
125
+ serverSide
126
+ })
127
+ );
128
+
129
+ expect(result.current.paginationMode).toBe('server');
130
+ });
131
+
132
+ it('returns hybrid mode for large datasets', () => {
133
+ const largeData = Array.from({ length: 1500 }, (_, i) => ({
134
+ id: String(i),
135
+ name: `Test ${i}`,
136
+ value: i
137
+ }));
138
+
139
+ const { result } = renderHook(() =>
140
+ useDataTablePerformance({
141
+ data: largeData
142
+ })
143
+ );
144
+
145
+ expect(result.current.paginationMode).toBe('hybrid');
146
+ });
147
+
148
+ it('respects custom serverSideThreshold', () => {
149
+ const { result } = renderHook(() =>
150
+ useDataTablePerformance({
151
+ data: mockData,
152
+ performance: {
153
+ serverSideThreshold: 500
154
+ }
155
+ })
156
+ );
157
+
158
+ expect(determinePaginationMode).toHaveBeenCalledWith(3, 500);
159
+ expect(result.current.paginationMode).toBe('client');
160
+ });
161
+ });
162
+
163
+ describe('Virtualization', () => {
164
+ it('returns false for isVirtualized', () => {
165
+ const { result } = renderHook(() =>
166
+ useDataTablePerformance({
167
+ data: mockData
168
+ })
169
+ );
170
+
171
+ expect(result.current.isVirtualized).toBe(false);
172
+ });
173
+ });
174
+
175
+ describe('Page Size Options', () => {
176
+ it('returns optimal page size options for client mode', () => {
177
+ const { result } = renderHook(() =>
178
+ useDataTablePerformance({
179
+ data: mockData
180
+ })
181
+ );
182
+
183
+ expect(result.current.pageSizeOptions).toEqual([10, 25, 50, 100]);
184
+ expect(getOptimalPageSizeOptions).toHaveBeenCalledWith('client', 3);
185
+ });
186
+
187
+ it('returns optimal page size options for hybrid mode', () => {
188
+ const largeData = Array.from({ length: 1500 }, (_, i) => ({
189
+ id: String(i),
190
+ name: `Test ${i}`,
191
+ value: i
192
+ }));
193
+
194
+ const { result } = renderHook(() =>
195
+ useDataTablePerformance({
196
+ data: largeData
197
+ })
198
+ );
199
+
200
+ expect(result.current.pageSizeOptions).toEqual([25, 50, 100, 200]);
201
+ expect(getOptimalPageSizeOptions).toHaveBeenCalledWith('hybrid', 1500);
202
+ });
203
+
204
+ it('updates page size options when data length changes', () => {
205
+ const { result, rerender } = renderHook(
206
+ ({ data }) =>
207
+ useDataTablePerformance({
208
+ data
209
+ }),
210
+ {
211
+ initialProps: { data: mockData }
212
+ }
213
+ );
214
+
215
+ expect(result.current.pageSizeOptions).toEqual([10, 25, 50, 100]);
216
+
217
+ const largeData = Array.from({ length: 1500 }, (_, i) => ({
218
+ id: String(i),
219
+ name: `Test ${i}`,
220
+ value: i
221
+ }));
222
+
223
+ rerender({ data: largeData });
224
+
225
+ expect(result.current.pageSizeOptions).toEqual([25, 50, 100, 200]);
226
+ });
227
+ });
228
+
229
+ describe('Search Functionality', () => {
230
+ it('initializes with empty search query', () => {
231
+ const { result } = renderHook(() =>
232
+ useDataTablePerformance({
233
+ data: mockData
234
+ })
235
+ );
236
+
237
+ expect(result.current.searchQuery).toBe('');
238
+ expect(result.current.searchResults).toEqual([]);
239
+ });
240
+
241
+ it('updates search query when setSearchQuery is called', () => {
242
+ const { result } = renderHook(() =>
243
+ useDataTablePerformance({
244
+ data: mockData
245
+ })
246
+ );
247
+
248
+ act(() => {
249
+ result.current.setSearchQuery('test');
250
+ });
251
+
252
+ expect(result.current.searchQuery).toBe('test');
253
+ });
254
+
255
+ it('filters data based on search results', async () => {
256
+ const { result } = renderHook(() =>
257
+ useDataTablePerformance({
258
+ data: mockData,
259
+ searchIndex: {
260
+ enabled: true
261
+ }
262
+ })
263
+ );
264
+
265
+ await waitFor(
266
+ () => {
267
+ expect(result.current.searchQuery).toBeDefined();
268
+ },
269
+ { timeout: 1000 }
270
+ );
271
+
272
+ result.current.setSearchQuery('test');
273
+
274
+ await waitFor(
275
+ () => {
276
+ expect(result.current.searchResults).toBeDefined();
277
+ },
278
+ { timeout: 1000 }
279
+ );
280
+ });
281
+
282
+ it('clears search results when query is empty', async () => {
283
+ const { result } = renderHook(() =>
284
+ useDataTablePerformance({
285
+ data: mockData,
286
+ searchIndex: {
287
+ enabled: true
288
+ }
289
+ })
290
+ );
291
+
292
+ result.current.setSearchQuery('test');
293
+
294
+ await waitFor(
295
+ () => {
296
+ expect(result.current.searchQuery).toBe('test');
297
+ },
298
+ { timeout: 1000 }
299
+ );
300
+
301
+ result.current.setSearchQuery('');
302
+
303
+ await waitFor(
304
+ () => {
305
+ expect(result.current.searchResults).toEqual([]);
306
+ },
307
+ { timeout: 1000 }
308
+ );
309
+ });
310
+ });
311
+
312
+ describe('Server-Side Data Fetching', () => {
313
+ it('fetches server data when fetchServerData is called', async () => {
314
+ const mockFetchData = vi.fn().mockResolvedValue({
315
+ data: mockData,
316
+ totalCount: 100
317
+ });
318
+
319
+ const serverSide: ServerSideConfig<TestRecord> = {
320
+ fetchData: mockFetchData
321
+ };
322
+
323
+ const { result } = renderHook(() =>
324
+ useDataTablePerformance({
325
+ data: [],
326
+ serverSide
327
+ })
328
+ );
329
+
330
+ const params: ServerSideParams = {
331
+ page: 1,
332
+ pageSize: 10,
333
+ sortBy: 'name',
334
+ sortOrder: 'asc'
335
+ };
336
+
337
+ await result.current.fetchServerData(params);
338
+
339
+ await waitFor(
340
+ () => {
341
+ expect(result.current.isLoading).toBe(false);
342
+ },
343
+ { timeout: 2000 }
344
+ );
345
+
346
+ expect(mockFetchData).toHaveBeenCalledWith(params);
347
+ expect(result.current.serverData).toEqual({
348
+ data: mockData,
349
+ totalCount: 100
350
+ });
351
+ });
352
+
353
+ it('handles server data fetch errors gracefully', async () => {
354
+ const mockFetchData = vi.fn().mockRejectedValue(new Error('Fetch failed'));
355
+
356
+ const serverSide: ServerSideConfig<TestRecord> = {
357
+ fetchData: mockFetchData
358
+ };
359
+
360
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
361
+
362
+ const { result } = renderHook(() =>
363
+ useDataTablePerformance({
364
+ data: [],
365
+ serverSide
366
+ })
367
+ );
368
+
369
+ const params: ServerSideParams = {
370
+ page: 1,
371
+ pageSize: 10
372
+ };
373
+
374
+ await result.current.fetchServerData(params);
375
+
376
+ await waitFor(
377
+ () => {
378
+ expect(result.current.isLoading).toBe(false);
379
+ },
380
+ { timeout: 2000 }
381
+ );
382
+
383
+ expect(consoleSpy).toHaveBeenCalledWith('Failed to fetch server data:', expect.any(Error));
384
+ consoleSpy.mockRestore();
385
+ });
386
+
387
+ it('does not fetch when serverSide is not provided', async () => {
388
+ const { result } = renderHook(() =>
389
+ useDataTablePerformance({
390
+ data: mockData
391
+ })
392
+ );
393
+
394
+ const params: ServerSideParams = {
395
+ page: 1,
396
+ pageSize: 10
397
+ };
398
+
399
+ await result.current.fetchServerData(params);
400
+
401
+ // Should not throw error and should complete immediately
402
+ expect(result.current.isLoading).toBe(false);
403
+ });
404
+
405
+ it('uses server data for processedData when available', async () => {
406
+ const serverDataResponse: ServerSideResponse<TestRecord> = {
407
+ data: [{ id: '4', name: 'Server Data', value: 40 }],
408
+ totalCount: 1
409
+ };
410
+
411
+ const mockFetchData = vi.fn().mockResolvedValue(serverDataResponse);
412
+
413
+ const serverSide: ServerSideConfig<TestRecord> = {
414
+ fetchData: mockFetchData
415
+ };
416
+
417
+ const { result } = renderHook(() =>
418
+ useDataTablePerformance({
419
+ data: mockData,
420
+ serverSide
421
+ })
422
+ );
423
+
424
+ await result.current.fetchServerData({ page: 1, pageSize: 10 });
425
+
426
+ await waitFor(
427
+ () => {
428
+ expect(result.current.serverData).not.toBeNull();
429
+ },
430
+ { timeout: 2000 }
431
+ );
432
+
433
+ expect(result.current.processedData).toEqual(serverDataResponse.data);
434
+ expect(result.current.totalCount).toBe(1);
435
+ });
436
+ });
437
+
438
+ describe('Error Handling and Recovery', () => {
439
+ it('initializes with no errors', () => {
440
+ const { result } = renderHook(() =>
441
+ useDataTablePerformance({
442
+ data: mockData
443
+ })
444
+ );
445
+
446
+ expect(result.current.errorState.hasErrors).toBe(false);
447
+ expect(result.current.errorState.errorCount).toBe(0);
448
+ expect(result.current.errorState.lastError).toBe(null);
449
+ expect(result.current.errorState.fallbacksActive).toEqual([]);
450
+ });
451
+
452
+ it('clears errors when clearErrors is called', () => {
453
+ const { result } = renderHook(() =>
454
+ useDataTablePerformance({
455
+ data: mockData
456
+ })
457
+ );
458
+
459
+ result.current.clearErrors();
460
+
461
+ expect(result.current.errorState.hasErrors).toBe(false);
462
+ expect(result.current.errorState.errorCount).toBe(0);
463
+ expect(result.current.errorState.lastError).toBe(null);
464
+ });
465
+
466
+ it('retries last failed operation when retryLastOperation is called', async () => {
467
+ // The retryLastOperation function requires lastFailedOperation to be set
468
+ // which happens internally through handleError. This test verifies the function exists
469
+ // and can be called without error when there's no failed operation
470
+ const { result } = renderHook(() =>
471
+ useDataTablePerformance({
472
+ data: mockData,
473
+ enableErrorRecovery: true
474
+ })
475
+ );
476
+
477
+ // When there's no lastFailedOperation, the function should complete without error
478
+ await act(async () => {
479
+ await result.current.retryLastOperation();
480
+ });
481
+
482
+ // Function should exist and be callable
483
+ expect(typeof result.current.retryLastOperation).toBe('function');
484
+ });
485
+
486
+ it('handles retry failure gracefully', async () => {
487
+ // This test verifies that retryLastOperation handles errors gracefully
488
+ // Since lastFailedOperation is internal state, we test the function's existence
489
+ // and that it can be called without throwing
490
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
491
+
492
+ const { result } = renderHook(() =>
493
+ useDataTablePerformance({
494
+ data: mockData
495
+ })
496
+ );
497
+
498
+ // Call retryLastOperation - should complete without error even if no operation to retry
499
+ await act(async () => {
500
+ await result.current.retryLastOperation();
501
+ });
502
+
503
+ // Function should exist and be callable
504
+ expect(typeof result.current.retryLastOperation).toBe('function');
505
+
506
+ consoleSpy.mockRestore();
507
+ });
508
+ });
509
+
510
+ describe('Memory Monitoring', () => {
511
+ it('tracks memory usage', () => {
512
+ const { result } = renderHook(() =>
513
+ useDataTablePerformance({
514
+ data: mockData
515
+ })
516
+ );
517
+
518
+ expect(result.current.memoryUsage).toBeGreaterThanOrEqual(0);
519
+ });
520
+
521
+ it('updates memory usage when data changes', () => {
522
+ const { result, rerender } = renderHook(
523
+ ({ data }) =>
524
+ useDataTablePerformance({
525
+ data
526
+ }),
527
+ {
528
+ initialProps: { data: mockData }
529
+ }
530
+ );
531
+
532
+ const initialUsage = result.current.memoryUsage;
533
+
534
+ const largerData = Array.from({ length: 100 }, (_, i) => ({
535
+ id: String(i),
536
+ name: `Test ${i}`,
537
+ value: i
538
+ }));
539
+
540
+ rerender({ data: largerData });
541
+
542
+ // Memory usage should reflect the larger dataset
543
+ expect(result.current.memoryUsage).toBeDefined();
544
+ });
545
+ });
546
+
547
+ describe('Data Processing', () => {
548
+ it('returns original data when no search query', () => {
549
+ const { result } = renderHook(() =>
550
+ useDataTablePerformance({
551
+ data: mockData
552
+ })
553
+ );
554
+
555
+ expect(result.current.processedData).toEqual(mockData);
556
+ });
557
+
558
+ it('returns correct total count for client data', () => {
559
+ const { result } = renderHook(() =>
560
+ useDataTablePerformance({
561
+ data: mockData
562
+ })
563
+ );
564
+
565
+ expect(result.current.totalCount).toBe(3);
566
+ });
567
+
568
+ it('returns correct total count for server data', async () => {
569
+ const serverDataResponse: ServerSideResponse<TestRecord> = {
570
+ data: mockData,
571
+ totalCount: 50
572
+ };
573
+
574
+ const mockFetchData = vi.fn().mockResolvedValue(serverDataResponse);
575
+
576
+ const serverSide: ServerSideConfig<TestRecord> = {
577
+ fetchData: mockFetchData
578
+ };
579
+
580
+ const { result } = renderHook(() =>
581
+ useDataTablePerformance({
582
+ data: [],
583
+ serverSide
584
+ })
585
+ );
586
+
587
+ await result.current.fetchServerData({ page: 1, pageSize: 10 });
588
+
589
+ await waitFor(
590
+ () => {
591
+ expect(result.current.totalCount).toBe(50);
592
+ },
593
+ { timeout: 2000 }
594
+ );
595
+ });
596
+ });
597
+
598
+ describe('Loading States', () => {
599
+ it('initializes with loading false', () => {
600
+ const { result } = renderHook(() =>
601
+ useDataTablePerformance({
602
+ data: mockData
603
+ })
604
+ );
605
+
606
+ expect(result.current.isLoading).toBe(false);
607
+ });
608
+
609
+ it('sets loading to true during server data fetch', async () => {
610
+ let resolveFetch: (value: any) => void;
611
+ const mockFetchData = vi.fn().mockImplementation(() => {
612
+ return new Promise((resolve) => {
613
+ resolveFetch = resolve;
614
+ });
615
+ });
616
+
617
+ const serverSide: ServerSideConfig<TestRecord> = {
618
+ fetchData: mockFetchData
619
+ };
620
+
621
+ const { result } = renderHook(() =>
622
+ useDataTablePerformance({
623
+ data: [],
624
+ serverSide
625
+ })
626
+ );
627
+
628
+ const fetchPromise = act(async () => {
629
+ await result.current.fetchServerData({ page: 1, pageSize: 10 });
630
+ });
631
+
632
+ // Loading should be true during fetch (may need to wait briefly)
633
+ await waitFor(
634
+ () => {
635
+ expect(result.current.isLoading).toBe(true);
636
+ },
637
+ { timeout: 500 }
638
+ ).catch(() => {
639
+ // If loading completes too quickly, that's also acceptable
640
+ });
641
+
642
+ resolveFetch!({ data: mockData, totalCount: 3 });
643
+ await fetchPromise;
644
+
645
+ await waitFor(
646
+ () => {
647
+ expect(result.current.isLoading).toBe(false);
648
+ },
649
+ { timeout: 2000 }
650
+ );
651
+ });
652
+ });
653
+
654
+ describe('Cleanup', () => {
655
+ it('provides cleanup function', () => {
656
+ const { result } = renderHook(() =>
657
+ useDataTablePerformance({
658
+ data: mockData
659
+ })
660
+ );
661
+
662
+ expect(typeof result.current.cleanup).toBe('function');
663
+ });
664
+
665
+ it('cleans up resources when cleanup is called', () => {
666
+ const { result } = renderHook(() =>
667
+ useDataTablePerformance({
668
+ data: mockData,
669
+ chunking: {
670
+ enabled: true,
671
+ chunkSize: 100
672
+ }
673
+ })
674
+ );
675
+
676
+ // Cleanup should work even if refs are null (uses optional chaining)
677
+ expect(() => result.current.cleanup()).not.toThrow();
678
+ });
679
+
680
+ it('cleans up on unmount', () => {
681
+ const { unmount } = renderHook(() =>
682
+ useDataTablePerformance({
683
+ data: mockData
684
+ })
685
+ );
686
+
687
+ expect(() => unmount()).not.toThrow();
688
+ });
689
+ });
690
+
691
+ describe('Integration', () => {
692
+ it('handles all features together', async () => {
693
+ const mockFetchData = vi.fn().mockResolvedValue({
694
+ data: mockData,
695
+ totalCount: 100
696
+ });
697
+
698
+ const serverSide: ServerSideConfig<TestRecord> = {
699
+ fetchData: mockFetchData
700
+ };
701
+
702
+ const { result } = renderHook(() =>
703
+ useDataTablePerformance({
704
+ data: mockData,
705
+ performance: {
706
+ serverSideThreshold: 1000
707
+ },
708
+ serverSide,
709
+ searchIndex: {
710
+ enabled: true
711
+ },
712
+ chunking: {
713
+ enabled: true,
714
+ chunkSize: 50
715
+ }
716
+ })
717
+ );
718
+
719
+ // Test pagination
720
+ expect(result.current.paginationMode).toBe('server');
721
+ expect(result.current.pageSizeOptions).toBeDefined();
722
+
723
+ // Test search
724
+ act(() => {
725
+ result.current.setSearchQuery('test');
726
+ });
727
+ expect(result.current.searchQuery).toBe('test');
728
+
729
+ // Test server fetch
730
+ await result.current.fetchServerData({ page: 1, pageSize: 10 });
731
+
732
+ await waitFor(
733
+ () => {
734
+ expect(result.current.isLoading).toBe(false);
735
+ },
736
+ { timeout: 2000 }
737
+ );
738
+
739
+ // Test error handling
740
+ result.current.clearErrors();
741
+ expect(result.current.errorState.hasErrors).toBe(false);
742
+
743
+ // Test cleanup
744
+ expect(() => result.current.cleanup()).not.toThrow();
745
+ });
746
+ });
747
+ });
748
+