@jmruthers/pace-core 0.5.4 → 0.5.5
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-ZQDRE46Q.js → DataTable-3SSI644S.js} +2 -2
- package/dist/{chunk-M4RW7PIP.js → chunk-2BJFM2JC.js} +105 -81
- package/dist/chunk-2BJFM2JC.js.map +1 -0
- package/dist/{chunk-5H3C2SWM.js → chunk-RTCA5ZNK.js} +2 -2
- package/dist/components.js +2 -2
- package/dist/index.js +2 -2
- package/dist/utils.js +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/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 +34 -34
- 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/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.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/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/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 +3 -3
- package/docs/implementation-guides/data-tables.md +20 -0
- package/docs/quick-reference.md +9 -0
- package/docs/rbac/examples.md +4 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/test-utils.tsx +147 -1
- package/src/components/DataTable/DataTable.tsx +20 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test 2.tsx +191 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +191 -0
- package/src/components/DataTable/components/DataTableCore.tsx +167 -138
- package/src/hooks/__tests__/hooks.integration.test.tsx +575 -0
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +115 -0
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +133 -0
- package/src/hooks/__tests__/useDebounce.unit.test.ts +82 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +293 -0
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +385 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +286 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +838 -0
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +104 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +633 -0
- package/src/hooks/__tests__/useRBAC.unit.test.ts +856 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +537 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +62 -0
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +37 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +428 -0
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +428 -0
- package/src/utils/__tests__/appConfig.unit.test.ts +55 -0
- package/src/utils/__tests__/audit.unit.test.ts +69 -0
- package/src/utils/__tests__/auth-utils.unit.test.ts +70 -0
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +317 -0
- package/src/utils/__tests__/cn.unit.test.ts +34 -0
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +503 -0
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +322 -0
- package/src/utils/__tests__/formatDate.unit.test.ts +109 -0
- package/src/utils/__tests__/formatting.unit.test.ts +66 -0
- package/src/utils/__tests__/index.unit.test.ts +251 -0
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +309 -0
- package/src/utils/__tests__/organisationContext.unit.test.ts +192 -0
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +259 -0
- package/src/utils/__tests__/permissionTypes.unit.test.ts +250 -0
- package/src/utils/__tests__/permissionUtils.unit.test.ts +362 -0
- package/src/utils/__tests__/sanitization.unit.test.ts +346 -0
- package/src/utils/__tests__/schemaUtils.unit.test.ts +441 -0
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +334 -0
- package/src/utils/__tests__/secureErrors.unit.test.ts +377 -0
- package/src/utils/__tests__/secureStorage.unit.test.ts +293 -0
- package/src/utils/__tests__/security.unit.test.ts +127 -0
- package/src/utils/__tests__/securityMonitor.unit.test.ts +280 -0
- package/src/utils/__tests__/sessionTracking.unit.test.ts +356 -0
- package/src/utils/__tests__/validation.unit.test.ts +84 -0
- package/src/utils/__tests__/validationUtils.unit.test.ts +571 -0
- package/src/validation/__tests__/common.unit.test.ts +101 -0
- package/src/validation/__tests__/csrf.unit.test.ts +302 -0
- package/src/validation/__tests__/passwordSchema.unit.test 2.ts +98 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +98 -0
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +466 -0
- package/dist/chunk-M4RW7PIP.js.map +0 -1
- /package/dist/{DataTable-ZQDRE46Q.js.map → DataTable-3SSI644S.js.map} +0 -0
- /package/dist/{chunk-5H3C2SWM.js.map → chunk-RTCA5ZNK.js.map} +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as Utils from '../index';
|
|
3
|
+
|
|
4
|
+
describe('utils index exports', () => {
|
|
5
|
+
describe('Core utilities', () => {
|
|
6
|
+
it('should export cn function', () => {
|
|
7
|
+
expect(Utils).toHaveProperty('cn');
|
|
8
|
+
expect(typeof Utils.cn).toBe('function');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Validation and sanitization', () => {
|
|
13
|
+
it('should export validation utilities', () => {
|
|
14
|
+
// These should be available from the validation module
|
|
15
|
+
expect(Utils).toHaveProperty('validateUserInput');
|
|
16
|
+
expect(Utils).toHaveProperty('emailSchema');
|
|
17
|
+
expect(Utils).toHaveProperty('passwordSchema');
|
|
18
|
+
expect(Utils).toHaveProperty('usernameSchema');
|
|
19
|
+
expect(Utils).toHaveProperty('nameSchema');
|
|
20
|
+
expect(Utils).toHaveProperty('phoneSchema');
|
|
21
|
+
expect(Utils).toHaveProperty('urlSchema');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should export sanitization utilities', () => {
|
|
25
|
+
// These should be available from the sanitization module
|
|
26
|
+
expect(Utils).toHaveProperty('sanitizeUserInput');
|
|
27
|
+
expect(Utils).toHaveProperty('sanitizeFormData');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('Security utilities', () => {
|
|
32
|
+
it('should export security functions', () => {
|
|
33
|
+
expect(Utils).toHaveProperty('getSecurityHeaders');
|
|
34
|
+
expect(Utils).toHaveProperty('validateSecurityHeaders');
|
|
35
|
+
expect(Utils).toHaveProperty('generateDeviceFingerprint');
|
|
36
|
+
expect(Utils).toHaveProperty('validateDeviceFingerprint');
|
|
37
|
+
expect(Utils).toHaveProperty('logSecurityEvent');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Accessibility testing utilities', () => {
|
|
42
|
+
it('should not export accessibility helpers in production builds', () => {
|
|
43
|
+
// Accessibility helpers are only available in test environments
|
|
44
|
+
// They are not exported in production builds to avoid including test dependencies
|
|
45
|
+
expect(Utils).not.toHaveProperty('a11yHelpers');
|
|
46
|
+
expect(Utils).not.toHaveProperty('runAccessibilityTests');
|
|
47
|
+
expect(Utils).not.toHaveProperty('screenReaderHelpers');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Performance utilities', () => {
|
|
52
|
+
it('should export performance monitoring functions', () => {
|
|
53
|
+
expect(Utils).toHaveProperty('useComponentPerformance');
|
|
54
|
+
expect(Utils).toHaveProperty('measureRenderPerformance');
|
|
55
|
+
expect(Utils).toHaveProperty('createPerformanceBenchmark');
|
|
56
|
+
expect(Utils).toHaveProperty('PERFORMANCE_THRESHOLDS');
|
|
57
|
+
// PerformanceMetrics is a type, not a runtime export
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Bundle analysis utilities', () => {
|
|
62
|
+
it('should export bundle analysis functions', () => {
|
|
63
|
+
expect(Utils).toHaveProperty('bundleAnalyzer');
|
|
64
|
+
expect(Utils).toHaveProperty('validateImportPattern');
|
|
65
|
+
expect(Utils).toHaveProperty('trackDynamicImport');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Dynamic utility loaders', () => {
|
|
70
|
+
it('should export dynamic loading functions', () => {
|
|
71
|
+
expect(Utils).toHaveProperty('loadLodash');
|
|
72
|
+
expect(Utils).toHaveProperty('loadDateUtils');
|
|
73
|
+
expect(Utils).toHaveProperty('loadChartUtils');
|
|
74
|
+
expect(Utils).toHaveProperty('loadFormUtils');
|
|
75
|
+
expect(Utils).toHaveProperty('loadCSVUtils');
|
|
76
|
+
expect(Utils).toHaveProperty('createLazyUtility');
|
|
77
|
+
expect(Utils).toHaveProperty('lazyLodash');
|
|
78
|
+
expect(Utils).toHaveProperty('lazyDateUtils');
|
|
79
|
+
expect(Utils).toHaveProperty('lazyChartUtils');
|
|
80
|
+
expect(Utils).toHaveProperty('lazyFormUtils');
|
|
81
|
+
expect(Utils).toHaveProperty('lazyCSVUtils');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Performance budget utilities', () => {
|
|
86
|
+
it('should export performance budget monitor', () => {
|
|
87
|
+
expect(Utils).toHaveProperty('performanceBudgetMonitor');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Lazy loading utilities', () => {
|
|
92
|
+
it('should export lazy loading functions', () => {
|
|
93
|
+
expect(Utils).toHaveProperty('createLazyComponent');
|
|
94
|
+
expect(Utils).toHaveProperty('LazyDataTable');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('App configuration utilities', () => {
|
|
99
|
+
it('should export app config functions', () => {
|
|
100
|
+
expect(Utils).toHaveProperty('setAppConfig');
|
|
101
|
+
expect(Utils).toHaveProperty('getAppConfig');
|
|
102
|
+
expect(Utils).toHaveProperty('getCurrentAppName');
|
|
103
|
+
expect(Utils).toHaveProperty('getCurrentAppId');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Permission utilities', () => {
|
|
108
|
+
it('should export permission functions', () => {
|
|
109
|
+
expect(Utils).toHaveProperty('transformPermissionMapToBoolean');
|
|
110
|
+
expect(Utils).toHaveProperty('hasPermission');
|
|
111
|
+
expect(Utils).toHaveProperty('hasAnyPermission');
|
|
112
|
+
expect(Utils).toHaveProperty('hasAllPermissions');
|
|
113
|
+
expect(Utils).toHaveProperty('PermissionType');
|
|
114
|
+
expect(Utils).toHaveProperty('parsePermission');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('Schema utilities', () => {
|
|
119
|
+
it('should export schema functions', () => {
|
|
120
|
+
expect(Utils).toHaveProperty('pickSchema');
|
|
121
|
+
expect(Utils).toHaveProperty('combineSchemas');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Security monitoring utilities', () => {
|
|
126
|
+
it('should export security monitoring functions', () => {
|
|
127
|
+
expect(Utils).toHaveProperty('securityMonitor');
|
|
128
|
+
expect(Utils).toHaveProperty('logSecurityEvent');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Session tracking utilities', () => {
|
|
133
|
+
it('should export session tracking functions', () => {
|
|
134
|
+
expect(Utils).toHaveProperty('useSessionTracking');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Audit utilities', () => {
|
|
139
|
+
it('should export audit functions', () => {
|
|
140
|
+
expect(Utils).toHaveProperty('auditLog');
|
|
141
|
+
expect(Utils).toHaveProperty('logAuditEvent');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('Device fingerprinting utilities', () => {
|
|
146
|
+
it('should export device fingerprinting functions', () => {
|
|
147
|
+
expect(Utils).toHaveProperty('generateDeviceFingerprint');
|
|
148
|
+
expect(Utils).toHaveProperty('validateDeviceFingerprint');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
describe('Formatting utilities', () => {
|
|
154
|
+
it('should export formatting functions', () => {
|
|
155
|
+
expect(Utils).toHaveProperty('formatDate');
|
|
156
|
+
expect(Utils).toHaveProperty('formatCurrency');
|
|
157
|
+
expect(Utils).toHaveProperty('formatNumber');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('Organisation context utilities', () => {
|
|
162
|
+
it('should export organisation context functions', () => {
|
|
163
|
+
expect(Utils).toHaveProperty('setOrganisationContext');
|
|
164
|
+
expect(Utils).toHaveProperty('clearOrganisationContext');
|
|
165
|
+
expect(Utils).toHaveProperty('getOrganisationContext');
|
|
166
|
+
expect(Utils).toHaveProperty('isOrganisationContextAvailable');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Integration tests', () => {
|
|
171
|
+
it('should allow importing specific utilities', () => {
|
|
172
|
+
// Test that we can import specific utilities
|
|
173
|
+
const { cn, validateUserInput, emailSchema } = Utils;
|
|
174
|
+
|
|
175
|
+
expect(typeof cn).toBe('function');
|
|
176
|
+
expect(typeof validateUserInput).toBe('function');
|
|
177
|
+
expect(emailSchema).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should allow importing utility groups', () => {
|
|
181
|
+
// Test that we can import groups of related utilities
|
|
182
|
+
const {
|
|
183
|
+
// Security
|
|
184
|
+
getSecurityHeaders,
|
|
185
|
+
generateDeviceFingerprint,
|
|
186
|
+
// Validation
|
|
187
|
+
emailSchema,
|
|
188
|
+
passwordSchema,
|
|
189
|
+
// Performance
|
|
190
|
+
useComponentPerformance,
|
|
191
|
+
// Permissions
|
|
192
|
+
hasPermission,
|
|
193
|
+
hasAnyPermission,
|
|
194
|
+
// Schema
|
|
195
|
+
pickSchema,
|
|
196
|
+
combineSchemas
|
|
197
|
+
} = Utils;
|
|
198
|
+
|
|
199
|
+
expect(typeof getSecurityHeaders).toBe('function');
|
|
200
|
+
expect(typeof generateDeviceFingerprint).toBe('function');
|
|
201
|
+
expect(emailSchema).toBeDefined();
|
|
202
|
+
expect(passwordSchema).toBeDefined();
|
|
203
|
+
expect(typeof useComponentPerformance).toBe('function');
|
|
204
|
+
expect(typeof hasPermission).toBe('function');
|
|
205
|
+
expect(typeof hasAnyPermission).toBe('function');
|
|
206
|
+
expect(typeof pickSchema).toBe('function');
|
|
207
|
+
expect(typeof combineSchemas).toBe('function');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should maintain function signatures', () => {
|
|
211
|
+
// Test that exported functions have the expected signatures
|
|
212
|
+
const { cn, validateUserInput, hasPermission } = Utils;
|
|
213
|
+
|
|
214
|
+
// cn should accept class names
|
|
215
|
+
expect(() => cn('class1', 'class2')).not.toThrow();
|
|
216
|
+
|
|
217
|
+
// validateUserInput should accept schema and data
|
|
218
|
+
expect(typeof validateUserInput).toBe('function');
|
|
219
|
+
|
|
220
|
+
// hasPermission should accept permissions and permission string
|
|
221
|
+
expect(typeof hasPermission).toBe('function');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should export all required utilities for a complete application', () => {
|
|
225
|
+
// Test that all major utility categories are exported
|
|
226
|
+
const categories = [
|
|
227
|
+
'cn', // Core utilities
|
|
228
|
+
'validateUserInput', // Validation
|
|
229
|
+
'sanitizeUserInput', // Sanitization
|
|
230
|
+
'getSecurityHeaders', // Security
|
|
231
|
+
'useComponentPerformance', // Performance
|
|
232
|
+
'bundleAnalyzer', // Bundle analysis
|
|
233
|
+
'loadLodash', // Dynamic loading
|
|
234
|
+
'performanceBudgetMonitor', // Performance budgets
|
|
235
|
+
'setAppConfig', // App config
|
|
236
|
+
'hasPermission', // Permissions
|
|
237
|
+
'pickSchema', // Schema utilities
|
|
238
|
+
'securityMonitor', // Security monitoring
|
|
239
|
+
'useSessionTracking', // Session tracking
|
|
240
|
+
'auditLog', // Audit
|
|
241
|
+
'generateDeviceFingerprint', // Device fingerprinting
|
|
242
|
+
'formatDate', // Formatting
|
|
243
|
+
'setOrganisationContext' // Organisation context
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
categories.forEach(utility => {
|
|
247
|
+
expect(Utils).toHaveProperty(utility);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file LazyLoad Utility Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/LazyLoad
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
10
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
11
|
+
import '@testing-library/jest-dom';
|
|
12
|
+
import { createLazyComponent, LazyDataTable } from '../lazyLoad';
|
|
13
|
+
import { LoadingSpinner } from '../../components/LoadingSpinner/LoadingSpinner';
|
|
14
|
+
import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
15
|
+
|
|
16
|
+
// Mock the DataTable component
|
|
17
|
+
vi.mock('../../components/DataTable', () => ({
|
|
18
|
+
DataTable: vi.fn(() => <div data-testid="data-table">DataTable Component</div>)
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Mock the LoadingSpinner component
|
|
22
|
+
vi.mock('../../components/LoadingSpinner/LoadingSpinner', () => ({
|
|
23
|
+
LoadingSpinner: vi.fn(() => <div data-testid="loading-spinner">Loading...</div>)
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
describe('LazyLoad Utility', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('createLazyComponent', () => {
|
|
32
|
+
it('should create a lazy component with default fallback', async () => {
|
|
33
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
34
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const LazyTestComponent = createLazyComponent(
|
|
38
|
+
mockImportFn,
|
|
39
|
+
'TestComponent'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
renderWithProviders(<LazyTestComponent />);
|
|
43
|
+
|
|
44
|
+
// Should show loading spinner initially
|
|
45
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
46
|
+
|
|
47
|
+
// Wait for the component to load
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(screen.getByTestId('lazy-component')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(mockImportFn).toHaveBeenCalledTimes(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should create a lazy component with custom fallback', async () => {
|
|
56
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
57
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const customFallback = <div data-testid="custom-fallback">Custom Loading...</div>;
|
|
61
|
+
|
|
62
|
+
const LazyTestComponent = createLazyComponent(
|
|
63
|
+
mockImportFn,
|
|
64
|
+
'TestComponent',
|
|
65
|
+
{ fallback: customFallback }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
renderWithProviders(<LazyTestComponent />);
|
|
69
|
+
|
|
70
|
+
// Should show custom fallback initially
|
|
71
|
+
expect(screen.getByTestId('custom-fallback')).toBeInTheDocument();
|
|
72
|
+
|
|
73
|
+
// Wait for the component to load
|
|
74
|
+
await waitFor(() => {
|
|
75
|
+
expect(screen.getByTestId('lazy-component')).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should create a lazy component with error boundary', async () => {
|
|
80
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
81
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const ErrorBoundary = ({ children }: { children: React.ReactNode }) => (
|
|
85
|
+
<div data-testid="error-boundary">{children}</div>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const LazyTestComponent = createLazyComponent(
|
|
89
|
+
mockImportFn,
|
|
90
|
+
'TestComponent',
|
|
91
|
+
{ errorBoundary: ErrorBoundary }
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
renderWithProviders(<LazyTestComponent />);
|
|
95
|
+
|
|
96
|
+
// Should show loading spinner initially
|
|
97
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
98
|
+
|
|
99
|
+
// Wait for the component to load
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(screen.getByTestId('lazy-component')).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Error boundary should wrap the component
|
|
105
|
+
expect(screen.getByTestId('error-boundary')).toBeInTheDocument();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it.skip('should handle import errors gracefully', async () => {
|
|
109
|
+
const mockImportFn = vi.fn().mockRejectedValue(new Error('Import failed'));
|
|
110
|
+
|
|
111
|
+
const LazyTestComponent = createLazyComponent(
|
|
112
|
+
mockImportFn,
|
|
113
|
+
'TestComponent'
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Suppress console.error and unhandledRejection for this test
|
|
117
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
118
|
+
const originalUnhandledRejection = process.listeners('unhandledRejection');
|
|
119
|
+
|
|
120
|
+
// Add a handler to catch the unhandled rejection
|
|
121
|
+
const unhandledRejectionHandler = (reason: any) => {
|
|
122
|
+
if (reason?.message === 'Import failed') {
|
|
123
|
+
// Expected error, ignore it
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Re-throw other errors
|
|
127
|
+
throw reason;
|
|
128
|
+
};
|
|
129
|
+
process.on('unhandledRejection', unhandledRejectionHandler);
|
|
130
|
+
|
|
131
|
+
renderWithProviders(<LazyTestComponent />);
|
|
132
|
+
|
|
133
|
+
// Should show loading spinner initially
|
|
134
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
135
|
+
|
|
136
|
+
// Wait for the error to be handled
|
|
137
|
+
await waitFor(() => {
|
|
138
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
139
|
+
}, { timeout: 1000 });
|
|
140
|
+
|
|
141
|
+
// Clean up
|
|
142
|
+
process.removeListener('unhandledRejection', unhandledRejectionHandler);
|
|
143
|
+
consoleSpy.mockRestore();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should set display name correctly', async () => {
|
|
147
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
148
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const LazyTestComponent = createLazyComponent(
|
|
152
|
+
mockImportFn,
|
|
153
|
+
'TestComponent'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(LazyTestComponent.displayName).toBe('LazyTestComponent');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should pass props to the lazy component', async () => {
|
|
160
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
161
|
+
default: ({ title, count }: { title: string; count: number }) => (
|
|
162
|
+
<div data-testid="lazy-component">
|
|
163
|
+
{title}: {count}
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const LazyTestComponent = createLazyComponent(
|
|
169
|
+
mockImportFn,
|
|
170
|
+
'TestComponent'
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
renderWithProviders(<LazyTestComponent title="Test" count={42} />);
|
|
174
|
+
|
|
175
|
+
await waitFor(() => {
|
|
176
|
+
expect(screen.getByTestId('lazy-component')).toHaveTextContent('Test: 42');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle multiple instances of the same lazy component', async () => {
|
|
181
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
182
|
+
default: ({ id }: { id: string }) => (
|
|
183
|
+
<div data-testid={`lazy-component-${id}`}>Component {id}</div>
|
|
184
|
+
)
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const LazyTestComponent = createLazyComponent(
|
|
188
|
+
mockImportFn,
|
|
189
|
+
'TestComponent'
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
renderWithProviders(
|
|
193
|
+
<div>
|
|
194
|
+
<LazyTestComponent id="1" />
|
|
195
|
+
<LazyTestComponent id="2" />
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
await waitFor(() => {
|
|
200
|
+
expect(screen.getByTestId('lazy-component-1')).toBeInTheDocument();
|
|
201
|
+
expect(screen.getByTestId('lazy-component-2')).toBeInTheDocument();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Import function should only be called once due to React.lazy caching
|
|
205
|
+
expect(mockImportFn).toHaveBeenCalledTimes(1);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('LazyDataTable', () => {
|
|
210
|
+
it('should render the DataTable component when loaded', async () => {
|
|
211
|
+
const mockProps = {
|
|
212
|
+
data: [{ id: 1, name: 'Test' }],
|
|
213
|
+
columns: [{ id: 'name', accessorKey: 'name', header: 'Name' }]
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
renderWithProviders(<LazyDataTable {...mockProps} />);
|
|
217
|
+
|
|
218
|
+
// Should show loading spinner initially
|
|
219
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
220
|
+
|
|
221
|
+
// Wait for the DataTable to load
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(screen.getByTestId('data-table')).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should pass props to the DataTable component', async () => {
|
|
228
|
+
const mockProps = {
|
|
229
|
+
data: [{ id: 1, name: 'Test' }],
|
|
230
|
+
columns: [{ id: 'name', accessorKey: 'name', header: 'Name' }]
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
renderWithProviders(<LazyDataTable {...mockProps} />);
|
|
234
|
+
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
expect(screen.getByTestId('data-table')).toBeInTheDocument();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should render without errors', () => {
|
|
241
|
+
const mockProps = {
|
|
242
|
+
data: [{ id: 1, name: 'Test' }],
|
|
243
|
+
columns: [{ id: 'name', accessorKey: 'name', header: 'Name' }]
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
expect(() => renderWithProviders(<LazyDataTable {...mockProps} />)).not.toThrow();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Component Integration', () => {
|
|
251
|
+
it('should work with React Suspense boundaries', async () => {
|
|
252
|
+
const mockImportFn = vi.fn().mockResolvedValue({
|
|
253
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const LazyTestComponent = createLazyComponent(
|
|
257
|
+
mockImportFn,
|
|
258
|
+
'TestComponent'
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const TestWrapper = () => (
|
|
262
|
+
<div data-testid="wrapper">
|
|
263
|
+
<LazyTestComponent />
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
renderWithProviders(<TestWrapper />);
|
|
268
|
+
|
|
269
|
+
// Should show loading spinner initially
|
|
270
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
271
|
+
|
|
272
|
+
// Wait for the component to load
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(screen.getByTestId('lazy-component')).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Wrapper should still be present
|
|
278
|
+
expect(screen.getByTestId('wrapper')).toBeInTheDocument();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle unmounting before load completes', async () => {
|
|
282
|
+
const mockImportFn = vi.fn().mockImplementation(() =>
|
|
283
|
+
new Promise(resolve => {
|
|
284
|
+
setTimeout(() => {
|
|
285
|
+
resolve({
|
|
286
|
+
default: () => <div data-testid="lazy-component">Lazy Component</div>
|
|
287
|
+
});
|
|
288
|
+
}, 100);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const LazyTestComponent = createLazyComponent(
|
|
293
|
+
mockImportFn,
|
|
294
|
+
'TestComponent'
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const { unmount } = renderWithProviders(<LazyTestComponent />);
|
|
298
|
+
|
|
299
|
+
// Should show loading spinner initially
|
|
300
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
301
|
+
|
|
302
|
+
// Unmount before load completes
|
|
303
|
+
unmount();
|
|
304
|
+
|
|
305
|
+
// Should not throw any errors
|
|
306
|
+
expect(mockImportFn).toHaveBeenCalledTimes(1);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|