@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.
- package/dist/{DataTable-HWZQGASI.js → DataTable-4GAVPIEG.js} +48 -30
- package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-BiNER8F5.d.ts} +28 -17
- package/dist/{chunk-33PHABLB.js → chunk-AFGTSUAD.js} +10 -127
- package/dist/chunk-AFGTSUAD.js.map +1 -0
- package/dist/{chunk-2DFZ432F.js → chunk-K34IM5CT.js} +3 -5
- package/dist/{chunk-2DFZ432F.js.map → chunk-K34IM5CT.js.map} +1 -1
- package/dist/{chunk-2CHATWBF.js → chunk-KHJS6VIA.js} +199 -35
- package/dist/chunk-KHJS6VIA.js.map +1 -0
- package/dist/{chunk-ZTT2AXMX.js → chunk-KK73ZB4E.js} +2 -2
- package/dist/{chunk-CY3AHGO4.js → chunk-M5IWZRBT.js} +1750 -2815
- package/dist/chunk-M5IWZRBT.js.map +1 -0
- package/dist/{chunk-DAXLNIDY.js → chunk-Y6TXWPJO.js} +6 -4
- package/dist/{chunk-DAXLNIDY.js.map → chunk-Y6TXWPJO.js.map} +1 -1
- package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
- package/dist/chunk-YCKPEMJA.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +7 -6
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +17 -40
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/rbac/index.d.ts +54 -1
- package/dist/rbac/index.js +5 -4
- package/dist/utils.js +1 -1
- package/docs/TERMINOLOGY.md +231 -0
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +47 -0
- package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +57 -11
- package/docs/api-reference/providers.md +26 -7
- package/docs/best-practices/README.md +20 -0
- package/docs/best-practices/accessibility.md +566 -0
- package/docs/best-practices/performance-expansion.md +473 -0
- package/docs/core-concepts/authentication.md +15 -7
- package/docs/documentation-index.md +1 -1
- package/docs/documentation-templates.md +539 -0
- package/docs/getting-started/quick-start.md +16 -66
- package/docs/implementation-guides/component-styling.md +410 -0
- package/docs/implementation-guides/data-tables.md +1 -1
- package/docs/style-guide.md +39 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
- package/src/__tests__/helpers/supabaseMock.ts +48 -2
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +73 -9
- package/src/components/DataTable/components/DataTableCore.tsx +280 -475
- package/src/components/DataTable/components/UnifiedTableBody.tsx +120 -153
- package/src/components/DataTable/components/index.ts +1 -2
- package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
- package/src/components/DataTable/core/index.ts +1 -8
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +525 -0
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
- package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
- package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
- package/src/components/DataTable/hooks/index.ts +6 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +149 -0
- package/src/components/DataTable/hooks/useDataTableState.ts +12 -6
- package/src/components/DataTable/hooks/useHierarchicalState.ts +26 -8
- package/src/components/DataTable/hooks/useTableColumns.ts +153 -0
- package/src/components/DataTable/index.ts +1 -9
- package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +3 -6
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +462 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +265 -0
- package/src/components/DataTable/utils/errorHandling.ts +52 -460
- package/src/components/DataTable/utils/exportUtils.ts +46 -15
- package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
- package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
- package/src/components/DataTable/utils/index.ts +5 -0
- package/src/components/DataTable/utils/rowUtils.ts +68 -0
- package/src/components/EventSelector/EventSelector.test.tsx +672 -0
- package/src/components/Label/__tests__/Label.test.tsx +434 -0
- package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
- package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
- package/src/components/Select/Select.test.tsx +143 -120
- package/src/components/Select/Select.tsx +47 -212
- package/src/components/Select/hooks.ts +36 -1
- package/src/components/Select/index.ts +2 -1
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +137 -0
- package/src/hooks/useSecureDataAccess.test.ts +32 -29
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
- package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
- package/src/rbac/hooks/index.ts +2 -0
- package/src/rbac/hooks/useResolvedScope.ts +232 -0
- package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
- package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
- package/src/types/__tests__/README.md +114 -0
- package/src/types/__tests__/validation.test.ts +731 -0
- package/src/utils/__tests__/file-reference.test.ts +383 -0
- package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
- package/src/utils/appNameResolver.test.ts +54 -0
- package/src/validation/__tests__/csrf.unit.test.ts +63 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
- package/dist/chunk-2CHATWBF.js.map +0 -1
- package/dist/chunk-33PHABLB.js.map +0 -1
- package/dist/chunk-CY3AHGO4.js.map +0 -1
- package/dist/chunk-TYHR5X4W.js +0 -33
- package/dist/chunk-TYHR5X4W.js.map +0 -1
- package/dist/chunk-YNUBMSMV.js.map +0 -1
- package/dist/eventContext-BBA42P6G.js +0 -14
- package/dist/eventContext-BBA42P6G.js.map +0 -1
- package/docs/documentation-style-checklist.md +0 -294
- package/src/components/DataTable/components/DataTableBody.tsx +0 -488
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
- package/src/components/DataTable/core/ActionManager.ts +0 -235
- package/src/components/DataTable/core/ColumnManager.ts +0 -215
- package/src/components/DataTable/core/DataManager.ts +0 -188
- package/src/components/DataTable/core/DataTableContext.tsx +0 -181
- package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
- package/src/components/DataTable/core/PluginRegistry.ts +0 -229
- package/src/components/DataTable/core/StateManager.ts +0 -311
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
- package/src/components/DataTable/core/interfaces.ts +0 -338
- package/src/components/DataTable/utils/debugTools.ts +0 -583
- package/src/components/Select/Select.bug-test.tsx +0 -69
- package/src/components/Select/Select.refactored.tsx +0 -497
- /package/dist/{DataTable-HWZQGASI.js.map → DataTable-4GAVPIEG.js.map} +0 -0
- /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
|
+
|