@jmruthers/pace-core 0.5.75 → 0.5.76

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 (226) hide show
  1. package/dist/{DataTable-HWZQGASI.js → DataTable-4GAVPIEG.js} +48 -30
  2. package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-BiNER8F5.d.ts} +28 -17
  3. package/dist/{chunk-33PHABLB.js → chunk-AFGTSUAD.js} +10 -127
  4. package/dist/chunk-AFGTSUAD.js.map +1 -0
  5. package/dist/{chunk-2DFZ432F.js → chunk-K34IM5CT.js} +3 -5
  6. package/dist/{chunk-2DFZ432F.js.map → chunk-K34IM5CT.js.map} +1 -1
  7. package/dist/{chunk-2CHATWBF.js → chunk-KHJS6VIA.js} +199 -35
  8. package/dist/chunk-KHJS6VIA.js.map +1 -0
  9. package/dist/{chunk-ZTT2AXMX.js → chunk-KK73ZB4E.js} +2 -2
  10. package/dist/{chunk-CY3AHGO4.js → chunk-M5IWZRBT.js} +1750 -2815
  11. package/dist/chunk-M5IWZRBT.js.map +1 -0
  12. package/dist/{chunk-DAXLNIDY.js → chunk-Y6TXWPJO.js} +6 -4
  13. package/dist/{chunk-DAXLNIDY.js.map → chunk-Y6TXWPJO.js.map} +1 -1
  14. package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
  15. package/dist/chunk-YCKPEMJA.js.map +1 -0
  16. package/dist/components.d.ts +1 -1
  17. package/dist/components.js +7 -6
  18. package/dist/components.js.map +1 -1
  19. package/dist/hooks.d.ts +17 -40
  20. package/dist/hooks.js +6 -6
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +12 -10
  23. package/dist/index.js.map +1 -1
  24. package/dist/rbac/index.d.ts +54 -1
  25. package/dist/rbac/index.js +5 -4
  26. package/dist/utils.js +1 -1
  27. package/docs/TERMINOLOGY.md +231 -0
  28. package/docs/api/classes/ColumnFactory.md +1 -1
  29. package/docs/api/classes/ErrorBoundary.md +1 -1
  30. package/docs/api/classes/InvalidScopeError.md +1 -1
  31. package/docs/api/classes/MissingUserContextError.md +1 -1
  32. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  33. package/docs/api/classes/PermissionDeniedError.md +1 -1
  34. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  35. package/docs/api/classes/RBACAuditManager.md +1 -1
  36. package/docs/api/classes/RBACCache.md +1 -1
  37. package/docs/api/classes/RBACEngine.md +1 -1
  38. package/docs/api/classes/RBACError.md +1 -1
  39. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  40. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  41. package/docs/api/classes/StorageUtils.md +1 -1
  42. package/docs/api/enums/FileCategory.md +1 -1
  43. package/docs/api/interfaces/AggregateConfig.md +1 -1
  44. package/docs/api/interfaces/ButtonProps.md +1 -1
  45. package/docs/api/interfaces/CardProps.md +1 -1
  46. package/docs/api/interfaces/ColorPalette.md +1 -1
  47. package/docs/api/interfaces/ColorShade.md +1 -1
  48. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  49. package/docs/api/interfaces/DataTableAction.md +1 -1
  50. package/docs/api/interfaces/DataTableColumn.md +1 -1
  51. package/docs/api/interfaces/DataTableProps.md +1 -1
  52. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  53. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  54. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  55. package/docs/api/interfaces/EventLogoProps.md +1 -1
  56. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  57. package/docs/api/interfaces/FileMetadata.md +1 -1
  58. package/docs/api/interfaces/FileReference.md +1 -1
  59. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  60. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  61. package/docs/api/interfaces/FileUploadProps.md +1 -1
  62. package/docs/api/interfaces/FooterProps.md +1 -1
  63. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  64. package/docs/api/interfaces/InputProps.md +1 -1
  65. package/docs/api/interfaces/LabelProps.md +1 -1
  66. package/docs/api/interfaces/LoginFormProps.md +1 -1
  67. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  68. package/docs/api/interfaces/NavigationContextType.md +1 -1
  69. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  70. package/docs/api/interfaces/NavigationItem.md +1 -1
  71. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  72. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  73. package/docs/api/interfaces/Organisation.md +1 -1
  74. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  75. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  76. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  77. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  78. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  79. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  80. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  81. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  82. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  83. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  84. package/docs/api/interfaces/PaletteData.md +1 -1
  85. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  86. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  87. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  88. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  89. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  90. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  91. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  92. package/docs/api/interfaces/RBACConfig.md +1 -1
  93. package/docs/api/interfaces/RBACContextType.md +1 -1
  94. package/docs/api/interfaces/RBACLogger.md +1 -1
  95. package/docs/api/interfaces/RBACProviderProps.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/UsePublicEventLogoOptions.md +1 -1
  119. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  120. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  121. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  123. package/docs/api/interfaces/UseResolvedScopeOptions.md +47 -0
  124. package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
  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 +57 -11
  129. package/docs/api-reference/providers.md +26 -7
  130. package/docs/best-practices/README.md +20 -0
  131. package/docs/best-practices/accessibility.md +566 -0
  132. package/docs/best-practices/performance-expansion.md +473 -0
  133. package/docs/core-concepts/authentication.md +15 -7
  134. package/docs/documentation-index.md +1 -1
  135. package/docs/documentation-templates.md +539 -0
  136. package/docs/getting-started/quick-start.md +16 -66
  137. package/docs/implementation-guides/component-styling.md +410 -0
  138. package/docs/implementation-guides/data-tables.md +1 -1
  139. package/docs/style-guide.md +39 -0
  140. package/package.json +1 -1
  141. package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
  142. package/src/__tests__/helpers/supabaseMock.ts +48 -2
  143. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
  144. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +73 -9
  145. package/src/components/DataTable/components/DataTableCore.tsx +280 -475
  146. package/src/components/DataTable/components/UnifiedTableBody.tsx +120 -153
  147. package/src/components/DataTable/components/index.ts +1 -2
  148. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
  149. package/src/components/DataTable/core/index.ts +1 -8
  150. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +525 -0
  151. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
  152. package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
  153. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
  154. package/src/components/DataTable/hooks/index.ts +6 -0
  155. package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
  156. package/src/components/DataTable/hooks/useDataTablePermissions.ts +149 -0
  157. package/src/components/DataTable/hooks/useDataTableState.ts +12 -6
  158. package/src/components/DataTable/hooks/useHierarchicalState.ts +26 -8
  159. package/src/components/DataTable/hooks/useTableColumns.ts +153 -0
  160. package/src/components/DataTable/index.ts +1 -9
  161. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
  162. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +3 -6
  163. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +462 -0
  164. package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
  165. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
  166. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
  167. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +265 -0
  168. package/src/components/DataTable/utils/errorHandling.ts +52 -460
  169. package/src/components/DataTable/utils/exportUtils.ts +46 -15
  170. package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
  171. package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
  172. package/src/components/DataTable/utils/index.ts +5 -0
  173. package/src/components/DataTable/utils/rowUtils.ts +68 -0
  174. package/src/components/EventSelector/EventSelector.test.tsx +672 -0
  175. package/src/components/Label/__tests__/Label.test.tsx +434 -0
  176. package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
  177. package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
  178. package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
  179. package/src/components/Select/Select.test.tsx +143 -120
  180. package/src/components/Select/Select.tsx +47 -212
  181. package/src/components/Select/hooks.ts +36 -1
  182. package/src/components/Select/index.ts +2 -1
  183. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +137 -0
  184. package/src/hooks/useSecureDataAccess.test.ts +32 -29
  185. package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
  186. package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
  187. package/src/rbac/hooks/index.ts +2 -0
  188. package/src/rbac/hooks/useResolvedScope.ts +232 -0
  189. package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
  190. package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
  191. package/src/types/__tests__/README.md +114 -0
  192. package/src/types/__tests__/validation.test.ts +731 -0
  193. package/src/utils/__tests__/file-reference.test.ts +383 -0
  194. package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
  195. package/src/utils/appNameResolver.test.ts +54 -0
  196. package/src/validation/__tests__/csrf.unit.test.ts +63 -0
  197. package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
  198. package/dist/chunk-2CHATWBF.js.map +0 -1
  199. package/dist/chunk-33PHABLB.js.map +0 -1
  200. package/dist/chunk-CY3AHGO4.js.map +0 -1
  201. package/dist/chunk-TYHR5X4W.js +0 -33
  202. package/dist/chunk-TYHR5X4W.js.map +0 -1
  203. package/dist/chunk-YNUBMSMV.js.map +0 -1
  204. package/dist/eventContext-BBA42P6G.js +0 -14
  205. package/dist/eventContext-BBA42P6G.js.map +0 -1
  206. package/docs/documentation-style-checklist.md +0 -294
  207. package/src/components/DataTable/components/DataTableBody.tsx +0 -488
  208. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
  209. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
  210. package/src/components/DataTable/core/ActionManager.ts +0 -235
  211. package/src/components/DataTable/core/ColumnManager.ts +0 -215
  212. package/src/components/DataTable/core/DataManager.ts +0 -188
  213. package/src/components/DataTable/core/DataTableContext.tsx +0 -181
  214. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
  215. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  216. package/src/components/DataTable/core/StateManager.ts +0 -311
  217. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
  218. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
  219. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
  220. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
  221. package/src/components/DataTable/core/interfaces.ts +0 -338
  222. package/src/components/DataTable/utils/debugTools.ts +0 -583
  223. package/src/components/Select/Select.bug-test.tsx +0 -69
  224. package/src/components/Select/Select.refactored.tsx +0 -497
  225. /package/dist/{DataTable-HWZQGASI.js.map → DataTable-4GAVPIEG.js.map} +0 -0
  226. /package/dist/{chunk-ZTT2AXMX.js.map → chunk-KK73ZB4E.js.map} +0 -0
@@ -0,0 +1,466 @@
1
+ /**
2
+ * @file Unit Tests for performanceUtils
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Utils/__tests__
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
8
+ import {
9
+ determinePaginationMode,
10
+ getOptimalPageSizeOptions,
11
+ chunkData,
12
+ DataChunkManager,
13
+ SearchIndex,
14
+ debounce,
15
+ throttle,
16
+ VisibilityTracker
17
+ } from '../performanceUtils';
18
+ import type { ChunkingConfig, SearchIndexConfig, DataRecord } from '../../types';
19
+
20
+ describe('[unit] determinePaginationMode', () => {
21
+ it('returns client for small datasets', () => {
22
+ expect(determinePaginationMode(100)).toBe('client');
23
+ expect(determinePaginationMode(500)).toBe('client');
24
+ expect(determinePaginationMode(999)).toBe('client');
25
+ });
26
+
27
+ it('returns hybrid for medium datasets', () => {
28
+ expect(determinePaginationMode(1001)).toBe('hybrid');
29
+ expect(determinePaginationMode(5000)).toBe('hybrid');
30
+ });
31
+
32
+ it('returns server for large datasets', () => {
33
+ expect(determinePaginationMode(10001)).toBe('server');
34
+ expect(determinePaginationMode(50000)).toBe('server');
35
+ });
36
+
37
+ it('allows custom serverSideThreshold', () => {
38
+ expect(determinePaginationMode(5000, 10000)).toBe('hybrid');
39
+ expect(determinePaginationMode(10001, 10000)).toBe('server');
40
+ });
41
+
42
+ it('handles edge at threshold', () => {
43
+ expect(determinePaginationMode(5000, 5000)).toBe('hybrid');
44
+ expect(determinePaginationMode(5001, 5000)).toBe('server');
45
+ });
46
+ });
47
+
48
+ describe('[unit] getOptimalPageSizeOptions', () => {
49
+ it('returns correct options for client mode', () => {
50
+ const options100 = getOptimalPageSizeOptions('client', 100);
51
+ expect(options100).toEqual([10, 25, 50]);
52
+
53
+ const options200 = getOptimalPageSizeOptions('client', 200);
54
+ expect(options200).toEqual([10, 25, 50, 100]);
55
+ });
56
+
57
+ it('returns correct options for hybrid mode', () => {
58
+ const options = getOptimalPageSizeOptions('hybrid', 5000);
59
+ expect(options).toEqual([50, 100, 250, 500]);
60
+ });
61
+
62
+ it('returns correct options for server mode', () => {
63
+ const options = getOptimalPageSizeOptions('server', 10000);
64
+ expect(options).toEqual([25, 50, 100, 250]);
65
+ });
66
+
67
+ it('returns default options for invalid mode', () => {
68
+ const options = getOptimalPageSizeOptions('invalid' as any, 1000);
69
+ expect(options).toEqual([10, 25, 50, 100]);
70
+ });
71
+ });
72
+
73
+ describe('[unit] chunkData', () => {
74
+ const testData: DataRecord[] = Array.from({ length: 25 }, (_, i) => ({ id: i + 1 }));
75
+
76
+ it('splits data into correct chunks', () => {
77
+ const chunks = chunkData(testData, 10);
78
+
79
+ expect(chunks.length).toBe(3);
80
+ expect(chunks[0].length).toBe(10);
81
+ expect(chunks[1].length).toBe(10);
82
+ expect(chunks[2].length).toBe(5);
83
+ });
84
+
85
+ it('handles chunk size larger than data', () => {
86
+ const chunks = chunkData(testData, 100);
87
+
88
+ expect(chunks.length).toBe(1);
89
+ expect(chunks[0].length).toBe(25);
90
+ });
91
+
92
+ it('handles exact division', () => {
93
+ const chunks = chunkData(testData, 5);
94
+
95
+ expect(chunks.length).toBe(5);
96
+ chunks.forEach(chunk => {
97
+ expect(chunk.length).toBe(5);
98
+ });
99
+ });
100
+
101
+ it('handles empty data', () => {
102
+ const chunks = chunkData([], 10);
103
+
104
+ expect(chunks).toEqual([]);
105
+ });
106
+
107
+ it('handles chunk size of 1', () => {
108
+ const chunks = chunkData(testData, 1);
109
+
110
+ expect(chunks.length).toBe(25);
111
+ chunks.forEach(chunk => {
112
+ expect(chunk.length).toBe(1);
113
+ });
114
+ });
115
+ });
116
+
117
+ describe('[unit] DataChunkManager', () => {
118
+ let manager: DataChunkManager<DataRecord>;
119
+ const testData: DataRecord[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1 }));
120
+ const config: ChunkingConfig = {
121
+ chunkSize: 100,
122
+ maxChunksInMemory: 3
123
+ };
124
+
125
+ beforeEach(() => {
126
+ manager = new DataChunkManager(config);
127
+ });
128
+
129
+ it('loads chunks on demand', () => {
130
+ const chunk = manager.getChunk(0, testData);
131
+
132
+ expect(chunk.length).toBe(100);
133
+ expect(chunk[0]).toEqual({ id: 1 });
134
+ expect(chunk[99]).toEqual({ id: 100 });
135
+ });
136
+
137
+ it('caches chunks in memory', () => {
138
+ const chunk1 = manager.getChunk(0, testData);
139
+ const chunk2 = manager.getChunk(0, testData);
140
+
141
+ // Should return same reference
142
+ expect(chunk1).toBe(chunk2);
143
+ });
144
+
145
+ it('evicts oldest chunks when at capacity', () => {
146
+ // Fill cache
147
+ manager.getChunk(0, testData); // Load chunk 0
148
+ manager.getChunk(1, testData); // Load chunk 1
149
+ manager.getChunk(2, testData); // Load chunk 2
150
+
151
+ // Now at capacity (3 chunks)
152
+ const chunk3 = manager.getChunk(3, testData); // Should evict chunk 0
153
+
154
+ expect(manager.getChunk(0, testData)).not.toBe(chunk3); // Chunk 0 was evicted
155
+ });
156
+
157
+ it('updates access order correctly', () => {
158
+ manager.getChunk(0, testData); // Oldest
159
+ manager.getChunk(1, testData); // Middle
160
+ manager.getChunk(2, testData); // Newest
161
+
162
+ manager.getChunk(0, testData); // Now 0 is most recently used
163
+
164
+ const chunk3 = manager.getChunk(3, testData); // Should evict 1 (now oldest)
165
+ expect(chunk3).toBeDefined();
166
+ });
167
+
168
+ it('handles last chunk correctly', () => {
169
+ const lastChunk = manager.getChunk(9, testData); // 10th chunk, 100 items each = last 100 items
170
+
171
+ expect(lastChunk.length).toBe(100);
172
+ expect(lastChunk[0]).toEqual({ id: 901 });
173
+ expect(lastChunk[99]).toEqual({ id: 1000 });
174
+ });
175
+
176
+ it('handles out of bounds chunk index', () => {
177
+ const chunk = manager.getChunk(20, testData); // Beyond data
178
+
179
+ expect(chunk.length).toBe(0);
180
+ });
181
+
182
+ it('clears all cached chunks', () => {
183
+ manager.getChunk(0, testData);
184
+ manager.getChunk(1, testData);
185
+
186
+ manager.clear();
187
+
188
+ // After clearing, the chunks should be recreated
189
+ const chunkAfterClear = manager.getChunk(0, testData);
190
+ // The chunk will be recreated, but we can't easily test the internal state
191
+ expect(chunkAfterClear.length).toBeGreaterThan(0); // Should reload
192
+ });
193
+
194
+ it('returns correct memory usage', () => {
195
+ manager.getChunk(0, testData);
196
+ manager.getChunk(1, testData);
197
+
198
+ const memoryUsage = manager.getMemoryUsage();
199
+
200
+ expect(typeof memoryUsage).toBe('number');
201
+ expect(memoryUsage).toBeGreaterThan(0);
202
+ });
203
+ });
204
+
205
+ describe('[unit] SearchIndex', () => {
206
+ let searchIndex: SearchIndex<DataRecord>;
207
+ const testData: DataRecord[] = [
208
+ { name: 'John Doe', email: 'john@example.com' },
209
+ { name: 'Jane Smith', email: 'jane@example.com' },
210
+ { name: 'Bob Johnson', email: 'bob@example.com' }
211
+ ];
212
+
213
+ const config: SearchIndexConfig = {
214
+ indexedFields: ['name', 'email'],
215
+ fuzzySearch: false
216
+ };
217
+
218
+ beforeEach(() => {
219
+ searchIndex = new SearchIndex(config);
220
+ searchIndex.buildIndex(testData);
221
+ });
222
+
223
+ it('builds index from data', () => {
224
+ // Index should be built in beforeEach
225
+ const results = searchIndex.search('John');
226
+
227
+ expect(results.length).toBeGreaterThan(0);
228
+ });
229
+
230
+ it('finds exact matches', () => {
231
+ const results = searchIndex.search('John Doe');
232
+
233
+ expect(results).toContain(0);
234
+ });
235
+
236
+ it('finds matches across multiple fields', () => {
237
+ const results = searchIndex.search('john@example.com');
238
+
239
+ expect(results).toContain(0);
240
+ });
241
+
242
+ it('handles empty search query', () => {
243
+ const results = searchIndex.search('');
244
+
245
+ expect(results).toEqual([]);
246
+ });
247
+
248
+ it('handles whitespace in query', () => {
249
+ const results = searchIndex.search(' John ');
250
+
251
+ expect(results).toContain(0);
252
+ });
253
+
254
+ it('handles case-insensitive search', () => {
255
+ const results = searchIndex.search('JANE');
256
+
257
+ expect(results).toContain(1);
258
+ });
259
+
260
+ it('handles partial word matches', () => {
261
+ const results = searchIndex.search('Jane');
262
+
263
+ expect(results).toContain(1);
264
+ });
265
+
266
+ it('requires all search terms to match with multiple words', () => {
267
+ const results = searchIndex.search('Jane Smith');
268
+
269
+ expect(results).toContain(1);
270
+
271
+ const noResults = searchIndex.search('Jane Johnson');
272
+
273
+ expect(noResults).not.toContain(1);
274
+ });
275
+
276
+ it('handles special characters in search', () => {
277
+ const results = searchIndex.search('john@example');
278
+
279
+ expect(results).toContain(0);
280
+ });
281
+
282
+ it('finds no results for non-matching query', () => {
283
+ const results = searchIndex.search('NonExistentUser');
284
+
285
+ expect(results).toEqual([]);
286
+ });
287
+ });
288
+
289
+ describe('[unit] SearchIndex with Fuzzy Search', () => {
290
+ it('finds fuzzy matches when enabled', () => {
291
+ const testData: DataRecord[] = [
292
+ { name: 'JavaScript' },
293
+ { name: 'TypeScript' },
294
+ { name: 'Python' }
295
+ ];
296
+
297
+ const fuzzyConfig: SearchIndexConfig = {
298
+ indexedFields: ['name'],
299
+ fuzzySearch: true,
300
+ fuzzyThreshold: 0.6
301
+ };
302
+
303
+ const searchIndex = new SearchIndex(fuzzyConfig);
304
+ searchIndex.buildIndex(testData);
305
+
306
+ const results = searchIndex.search('JavScript'); // Typo
307
+
308
+ expect(results.length).toBeGreaterThan(0);
309
+ });
310
+
311
+ it('respects fuzzy threshold', () => {
312
+ const testData: DataRecord[] = [
313
+ { name: 'test' }
314
+ ];
315
+
316
+ const fuzzyConfig: SearchIndexConfig = {
317
+ indexedFields: ['name'],
318
+ fuzzySearch: true,
319
+ fuzzyThreshold: 0.8 // High threshold
320
+ };
321
+
322
+ const searchIndex = new SearchIndex(fuzzyConfig);
323
+ searchIndex.buildIndex(testData);
324
+
325
+ const results = searchIndex.search('testt');
326
+
327
+ // Should still match due to high similarity
328
+ expect(results.length).toBeGreaterThan(0);
329
+ });
330
+
331
+ it('does not match very different strings', () => {
332
+ const testData: DataRecord[] = [
333
+ { name: 'test' }
334
+ ];
335
+
336
+ const fuzzyConfig: SearchIndexConfig = {
337
+ indexedFields: ['name'],
338
+ fuzzySearch: true,
339
+ fuzzyThreshold: 0.8
340
+ };
341
+
342
+ const searchIndex = new SearchIndex(fuzzyConfig);
343
+ searchIndex.buildIndex(testData);
344
+
345
+ const results = searchIndex.search('completelydifferent');
346
+
347
+ expect(results.length).toBe(0);
348
+ });
349
+ });
350
+
351
+ describe('[unit] debounce', () => {
352
+ beforeEach(() => {
353
+ vi.useFakeTimers();
354
+ });
355
+
356
+ afterEach(() => {
357
+ vi.useRealTimers();
358
+ });
359
+
360
+ it('delays function execution', () => {
361
+ const fn = vi.fn();
362
+ const debouncedFn = debounce(fn, 100);
363
+
364
+ debouncedFn();
365
+ expect(fn).not.toHaveBeenCalled();
366
+
367
+ vi.advanceTimersByTime(100);
368
+ expect(fn).toHaveBeenCalledTimes(1);
369
+ });
370
+
371
+ it('cancels previous calls when called multiple times', () => {
372
+ const fn = vi.fn();
373
+ const debouncedFn = debounce(fn, 100);
374
+
375
+ debouncedFn();
376
+ debouncedFn();
377
+ debouncedFn();
378
+
379
+ vi.advanceTimersByTime(100);
380
+ expect(fn).toHaveBeenCalledTimes(1);
381
+ });
382
+
383
+ it('preserves function arguments', () => {
384
+ const fn = vi.fn();
385
+ const debouncedFn = debounce(fn, 100);
386
+
387
+ debouncedFn('arg1', 'arg2');
388
+
389
+ vi.advanceTimersByTime(100);
390
+ expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
391
+ });
392
+ });
393
+
394
+ describe('[unit] throttle', () => {
395
+ beforeEach(() => {
396
+ vi.useFakeTimers();
397
+ });
398
+
399
+ afterEach(() => {
400
+ vi.useRealTimers();
401
+ });
402
+
403
+ it('limits function execution rate', () => {
404
+ const fn = vi.fn();
405
+ const throttledFn = throttle(fn, 100);
406
+
407
+ throttledFn();
408
+ expect(fn).toHaveBeenCalledTimes(1);
409
+
410
+ throttledFn();
411
+ throttledFn();
412
+ throttledFn();
413
+
414
+ // Should not be called again within throttle period
415
+ expect(fn).toHaveBeenCalledTimes(1);
416
+
417
+ // After throttle period
418
+ vi.advanceTimersByTime(100);
419
+ throttledFn();
420
+ expect(fn).toHaveBeenCalledTimes(2);
421
+ });
422
+
423
+ it('preserves function arguments', () => {
424
+ const fn = vi.fn();
425
+ const throttledFn = throttle(fn, 100);
426
+
427
+ throttledFn('arg1', 'arg2');
428
+ expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
429
+ });
430
+ });
431
+
432
+ describe('[unit] VisibilityTracker', () => {
433
+ it('creates tracker successfully', () => {
434
+ const tracker = new VisibilityTracker();
435
+
436
+ expect(tracker).toBeDefined();
437
+ });
438
+
439
+ it('tracks visible elements count', () => {
440
+ const tracker = new VisibilityTracker();
441
+
442
+ // In test environment, IntersectionObserver may not be available
443
+ // So we can only test basic instantiation
444
+ expect(tracker.getVisibleCount()).toBe(0);
445
+ });
446
+
447
+ it('provides callback for visibility changes', () => {
448
+ const tracker = new VisibilityTracker();
449
+ const callback = vi.fn();
450
+
451
+ const unsubscribe = tracker.onVisibilityChange(callback);
452
+
453
+ // Cleanup
454
+ unsubscribe();
455
+ });
456
+
457
+ it('destroys tracker cleanly', () => {
458
+ const tracker = new VisibilityTracker();
459
+
460
+ tracker.destroy();
461
+
462
+ // Should not throw
463
+ expect(() => tracker.getVisibleCount()).not.toThrow();
464
+ });
465
+ });
466
+