@jmruthers/pace-core 0.5.61 → 0.5.63

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 (164) hide show
  1. package/dist/{DataTable-5M6MV2VY.js → DataTable-7BER7PDS.js} +6 -6
  2. package/dist/{DataTable-DqDDvBfI.d.ts → DataTable-D15XipLZ.d.ts} +7 -0
  3. package/dist/{PublicLoadingSpinner-CrMOrhNz.d.ts → PublicLoadingSpinner-CXJ-W9wZ.d.ts} +2 -2
  4. package/dist/{chunk-44SAHU2N.js → chunk-2LPYEFXI.js} +5 -5
  5. package/dist/chunk-4BWGRQBG.js +74 -0
  6. package/dist/chunk-4BWGRQBG.js.map +1 -0
  7. package/dist/{chunk-XMTHMOOM.js → chunk-BTCA3ENN.js} +4 -4
  8. package/dist/{chunk-ESXTFEE6.js → chunk-C7GUF747.js} +3 -3
  9. package/dist/{chunk-W7PPXKTZ.js → chunk-CKNY7HYS.js} +2 -2
  10. package/dist/{chunk-5MLDIGHB.js → chunk-FVDOEGGG.js} +3 -3
  11. package/dist/{chunk-HFDYTSAP.js → chunk-QVEOQVD4.js} +3 -3
  12. package/dist/{chunk-XDXG6QVH.js → chunk-S66AJVI2.js} +13 -6
  13. package/dist/chunk-S66AJVI2.js.map +1 -0
  14. package/dist/{chunk-E4FPK232.js → chunk-T2MQY57J.js} +2 -2
  15. package/dist/{chunk-4ULBJNIT.js → chunk-T6HVDA24.js} +2 -2
  16. package/dist/{chunk-STT7INZR.js → chunk-ULBI5JGB.js} +2 -1
  17. package/dist/{chunk-CGSYCF2W.js → chunk-VTJ5HCZB.js} +2 -2
  18. package/dist/components.d.ts +82 -5
  19. package/dist/components.js +258 -9
  20. package/dist/components.js.map +1 -1
  21. package/dist/{appConfig-DjpeG6P-.d.ts → formatting-BfDeV-ja.d.ts} +29 -1
  22. package/dist/hooks.d.ts +3 -2
  23. package/dist/hooks.js +5 -5
  24. package/dist/index.d.ts +8 -11
  25. package/dist/index.js +25 -14
  26. package/dist/index.js.map +1 -1
  27. package/dist/{organisation-DD0yBbGU.d.ts → organisation-t-vvQC3g.d.ts} +1 -1
  28. package/dist/providers.d.ts +2 -2
  29. package/dist/providers.js +4 -4
  30. package/dist/rbac/index.js +6 -6
  31. package/dist/types.js +1 -1
  32. package/dist/{usePublicRouteParams-Cu6oKazv.d.ts → usePublicRouteParams-CdoFxnJK.d.ts} +2 -63
  33. package/dist/useToast-Bm6TnSK-.d.ts +63 -0
  34. package/dist/utils.d.ts +3 -31
  35. package/dist/utils.js +8 -41
  36. package/dist/utils.js.map +1 -1
  37. package/docs/api/classes/ErrorBoundary.md +1 -1
  38. package/docs/api/classes/InvalidScopeError.md +1 -1
  39. package/docs/api/classes/MissingUserContextError.md +1 -1
  40. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  41. package/docs/api/classes/PermissionDeniedError.md +1 -1
  42. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  43. package/docs/api/classes/RBACAuditManager.md +1 -1
  44. package/docs/api/classes/RBACCache.md +1 -1
  45. package/docs/api/classes/RBACEngine.md +1 -1
  46. package/docs/api/classes/RBACError.md +1 -1
  47. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  48. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  49. package/docs/api/classes/StorageUtils.md +1 -1
  50. package/docs/api/interfaces/AggregateConfig.md +1 -1
  51. package/docs/api/interfaces/ButtonProps.md +1 -1
  52. package/docs/api/interfaces/CardProps.md +1 -1
  53. package/docs/api/interfaces/ColorPalette.md +1 -1
  54. package/docs/api/interfaces/ColorShade.md +1 -1
  55. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  56. package/docs/api/interfaces/DataTableAction.md +1 -1
  57. package/docs/api/interfaces/DataTableColumn.md +1 -1
  58. package/docs/api/interfaces/DataTableProps.md +44 -18
  59. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  60. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  61. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  62. package/docs/api/interfaces/EventContextType.md +1 -1
  63. package/docs/api/interfaces/EventLogoProps.md +1 -1
  64. package/docs/api/interfaces/EventProviderProps.md +1 -1
  65. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  66. package/docs/api/interfaces/FileUploadProps.md +1 -1
  67. package/docs/api/interfaces/FooterProps.md +1 -1
  68. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  69. package/docs/api/interfaces/InputProps.md +1 -1
  70. package/docs/api/interfaces/LabelProps.md +1 -1
  71. package/docs/api/interfaces/LoginFormProps.md +1 -1
  72. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  73. package/docs/api/interfaces/NavigationContextType.md +1 -1
  74. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  75. package/docs/api/interfaces/NavigationItem.md +1 -1
  76. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  77. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  78. package/docs/api/interfaces/Organisation.md +1 -1
  79. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  80. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  81. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  82. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  83. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  84. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  85. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  86. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  87. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  88. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  89. package/docs/api/interfaces/PaletteData.md +1 -1
  90. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  92. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  93. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  96. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  97. package/docs/api/interfaces/RBACConfig.md +1 -1
  98. package/docs/api/interfaces/RBACContextType.md +1 -1
  99. package/docs/api/interfaces/RBACLogger.md +1 -1
  100. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  101. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  102. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  103. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  104. package/docs/api/interfaces/RouteConfig.md +1 -1
  105. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  106. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  107. package/docs/api/interfaces/StorageConfig.md +1 -1
  108. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  109. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  110. package/docs/api/interfaces/StorageListOptions.md +1 -1
  111. package/docs/api/interfaces/StorageListResult.md +1 -1
  112. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  113. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  114. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  115. package/docs/api/interfaces/StyleImport.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/UsePublicEventLogoOptions.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  124. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  125. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  127. package/docs/api/interfaces/UserEventAccess.md +1 -1
  128. package/docs/api/interfaces/UserMenuProps.md +1 -1
  129. package/docs/api/interfaces/UserProfile.md +1 -1
  130. package/docs/api/modules.md +148 -26
  131. package/docs/implementation-guides/data-tables.md +67 -0
  132. package/package.json +1 -1
  133. package/src/components/DataTable/DataTable.tsx +13 -0
  134. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +414 -0
  135. package/src/components/DataTable/components/DataTableCore.tsx +19 -2
  136. package/src/components/DataTable/types.ts +9 -0
  137. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +151 -0
  138. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
  139. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +287 -0
  140. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +861 -0
  141. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +628 -0
  142. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +777 -0
  143. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +901 -0
  144. package/src/components/Toast/index.ts +2 -0
  145. package/src/components/index.ts +15 -0
  146. package/src/hooks/__tests__/useApiFetch.unit.test.ts +1 -1
  147. package/src/hooks/useFileReference.ts +37 -0
  148. package/src/index.ts +10 -0
  149. package/src/styles/base.css +208 -0
  150. package/src/styles/semantic.css +24 -0
  151. package/dist/chunk-W66AZIOH.js +0 -29
  152. package/dist/chunk-W66AZIOH.js.map +0 -1
  153. package/dist/chunk-XDXG6QVH.js.map +0 -1
  154. /package/dist/{DataTable-5M6MV2VY.js.map → DataTable-7BER7PDS.js.map} +0 -0
  155. /package/dist/{chunk-44SAHU2N.js.map → chunk-2LPYEFXI.js.map} +0 -0
  156. /package/dist/{chunk-XMTHMOOM.js.map → chunk-BTCA3ENN.js.map} +0 -0
  157. /package/dist/{chunk-ESXTFEE6.js.map → chunk-C7GUF747.js.map} +0 -0
  158. /package/dist/{chunk-W7PPXKTZ.js.map → chunk-CKNY7HYS.js.map} +0 -0
  159. /package/dist/{chunk-5MLDIGHB.js.map → chunk-FVDOEGGG.js.map} +0 -0
  160. /package/dist/{chunk-HFDYTSAP.js.map → chunk-QVEOQVD4.js.map} +0 -0
  161. /package/dist/{chunk-E4FPK232.js.map → chunk-T2MQY57J.js.map} +0 -0
  162. /package/dist/{chunk-4ULBJNIT.js.map → chunk-T6HVDA24.js.map} +0 -0
  163. /package/dist/{chunk-STT7INZR.js.map → chunk-ULBI5JGB.js.map} +0 -0
  164. /package/dist/{chunk-CGSYCF2W.js.map → chunk-VTJ5HCZB.js.map} +0 -0
@@ -0,0 +1,628 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+ import '@testing-library/jest-dom';
6
+ // Define Operation type locally since old RBAC types are removed
7
+ type Operation = 'read' | 'create' | 'update' | 'delete' | 'manage';
8
+
9
+ // Mock React Router hooks
10
+ const mockNavigate = vi.fn();
11
+ const mockLocation = { pathname: '/test-path' };
12
+ vi.mock('react-router-dom', async () => {
13
+ const actual = await vi.importActual('react-router-dom');
14
+ return {
15
+ ...actual,
16
+ useNavigate: () => mockNavigate,
17
+ useLocation: () => mockLocation,
18
+ Outlet: () => <div data-testid="mock-outlet">Mock Outlet Content</div>
19
+ };
20
+ });
21
+
22
+ // Mock UnifiedAuthProvider
23
+ const mockSignOut = vi.fn().mockResolvedValue({ error: null });
24
+ const mockUpdatePassword = vi.fn().mockResolvedValue({ error: null });
25
+ const mockUser = {
26
+ id: 'test-user-id',
27
+ email: 'test@example.com',
28
+ user_metadata: {
29
+ display_name: 'Test User',
30
+ organisationId: 'test-org-123',
31
+ eventId: 'test-event-456',
32
+ appId: 'test-app-789'
33
+ }
34
+ };
35
+
36
+ vi.mock('../../../providers/UnifiedAuthProvider', () => ({
37
+ useUnifiedAuth: () => ({
38
+ user: mockUser,
39
+ signOut: mockSignOut,
40
+ updatePassword: mockUpdatePassword
41
+ })
42
+ }));
43
+
44
+ // Mock OrganisationProvider
45
+ const mockOrganisation = {
46
+ id: 'test-org-id',
47
+ name: 'Test Organisation',
48
+ display_name: 'Test Organisation',
49
+ description: 'Test organisation for testing',
50
+ subscription_tier: 'basic',
51
+ settings: {},
52
+ is_active: true,
53
+ created_at: '2023-01-01T00:00:00Z',
54
+ updated_at: '2023-01-01T00:00:00Z'
55
+ };
56
+
57
+ const mockOrganisationContext = {
58
+ selectedOrganisation: mockOrganisation,
59
+ organisations: [mockOrganisation],
60
+ userMemberships: [{
61
+ id: 'test-membership-id',
62
+ user_id: 'test-user-id',
63
+ organisation_id: 'test-org-id',
64
+ role: 'org_admin',
65
+ granted_at: '2023-01-01T00:00:00Z',
66
+ status: 'active' as const,
67
+ created_at: '2023-01-01T00:00:00Z',
68
+ updated_at: '2023-01-01T00:00:00Z'
69
+ }],
70
+ isLoading: false,
71
+ error: null,
72
+ hasValidOrganisationContext: true,
73
+ setSelectedOrganisation: vi.fn(),
74
+ switchOrganisation: vi.fn().mockResolvedValue(undefined),
75
+ getUserRole: vi.fn().mockReturnValue('member'),
76
+ validateOrganisationAccess: vi.fn().mockReturnValue(true),
77
+ ensureOrganisationContext: vi.fn().mockReturnValue(mockOrganisation),
78
+ refreshOrganisations: vi.fn().mockResolvedValue(undefined),
79
+ getPrimaryOrganisation: vi.fn().mockReturnValue(mockOrganisation),
80
+ isOrganisationSecure: vi.fn().mockReturnValue(true)
81
+ };
82
+
83
+ vi.mock('../../../providers/OrganisationProvider', () => ({
84
+ OrganisationProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
85
+ useOrganisations: () => mockOrganisationContext
86
+ }));
87
+
88
+ // Mock the new RBAC system for performance testing
89
+ const mockIsPermitted = vi.fn().mockResolvedValue(true);
90
+ const mockCheckPermission = vi.fn().mockResolvedValue(true);
91
+
92
+ vi.mock('../../../rbac/api', () => ({
93
+ isPermitted: vi.fn().mockResolvedValue(true),
94
+ getPermissionMap: vi.fn().mockResolvedValue({}),
95
+ getAccessLevel: vi.fn().mockResolvedValue('viewer'),
96
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
97
+ setupRBAC: vi.fn()
98
+ }));
99
+
100
+ // Mock child components
101
+ vi.mock('../../Header', () => ({
102
+ Header: vi.fn(({ appName, user, onSignOut, onChangePassword, onNavigate, currentPath, logo, userMenu, actions, navItems }) => (
103
+ <header data-testid="mock-header" role="banner">
104
+ <div data-testid="app-name">{appName ?? 'Test App'}</div>
105
+ <div data-testid="user-info">{user?.user_metadata?.display_name || user?.email}</div>
106
+ <nav data-testid="navigation">
107
+ {navItems?.map((item, index) => (
108
+ <button
109
+ key={item.id}
110
+ data-testid={`nav-nav-${index}`}
111
+ onClick={() => onNavigate(item)}
112
+ >
113
+ {item.label}
114
+ </button>
115
+ )) || (
116
+ <>
117
+ <button
118
+ data-testid="home-nav"
119
+ onClick={() => onNavigate({ id: 'home', label: 'Home', href: '/' })}
120
+ >
121
+ Home
122
+ </button>
123
+ <button
124
+ data-testid="dashboard-nav"
125
+ onClick={() => onNavigate({ id: 'dashboard', label: 'Dashboard', href: '/dashboard' })}
126
+ >
127
+ Dashboard
128
+ </button>
129
+ </>
130
+ )}
131
+ </nav>
132
+ {logo && logo}
133
+ {userMenu && userMenu}
134
+ {actions && actions}
135
+ <button
136
+ data-testid="sign-out-button"
137
+ onClick={() => onSignOut()}
138
+ >
139
+ Sign Out
140
+ </button>
141
+ <button
142
+ data-testid="change-password-button"
143
+ onClick={() => onChangePassword('newpassword123')}
144
+ >
145
+ Change Password
146
+ </button>
147
+ <div data-testid="current-path">{currentPath}</div>
148
+ </header>
149
+ ))
150
+ }));
151
+
152
+ vi.mock('../../Footer', () => ({
153
+ Footer: vi.fn(() => <footer data-testid="mock-footer" role="contentinfo">Mock Footer</footer>)
154
+ }));
155
+
156
+ // Mock window.location
157
+ Object.defineProperty(window, 'location', {
158
+ value: {
159
+ pathname: '/test-path'
160
+ },
161
+ writable: true
162
+ });
163
+
164
+ import { PaceAppLayout } from '../PaceAppLayout';
165
+
166
+ // Wrapper component to provide Router context
167
+ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
168
+ <BrowserRouter>
169
+ {children}
170
+ </BrowserRouter>
171
+ );
172
+
173
+ // Performance thresholds
174
+ const PERFORMANCE_THRESHOLDS = {
175
+ RENDER_TIME: 200, // ms - Increased due to migration changes requiring more complex organization loading
176
+ PERMISSION_CHECK_TIME: 110, // ms - Increased to account for timing variations
177
+ MEMORY_USAGE_INCREASE: 1024 * 1024, // 1MB
178
+ RE_RENDER_COUNT: 3
179
+ };
180
+
181
+ describe('PaceAppLayout Performance', () => {
182
+ beforeEach(() => {
183
+ // Reset all mocks before each test
184
+ vi.clearAllMocks();
185
+ mockSignOut.mockResolvedValue({ error: null });
186
+ mockUpdatePassword.mockResolvedValue({ error: null });
187
+ mockCheckPermission.mockClear();
188
+ mockCheckPermission.mockResolvedValue(true);
189
+ mockIsPermitted.mockClear();
190
+ mockIsPermitted.mockResolvedValue(true);
191
+ });
192
+
193
+ describe('Rendering Performance', () => {
194
+ it('renders within performance threshold', () => {
195
+ const startTime = performance.now();
196
+
197
+ render(
198
+ <TestWrapper>
199
+ <PaceAppLayout appName="Test App" />
200
+ </TestWrapper>
201
+ );
202
+
203
+ const endTime = performance.now();
204
+ const renderTime = endTime - startTime;
205
+
206
+ expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
207
+ expect(screen.getByTestId('mock-header')).toBeInTheDocument();
208
+ });
209
+
210
+ it('renders with custom components within threshold', () => {
211
+ const CustomLogo = () => <div data-testid="custom-logo">Custom Logo</div>;
212
+ const CustomUserMenu = () => <div data-testid="custom-user-menu">Custom Menu</div>;
213
+
214
+ const startTime = performance.now();
215
+
216
+ render(
217
+ <TestWrapper>
218
+ <PaceAppLayout
219
+ appName="Test App"
220
+ customLogo={<CustomLogo />}
221
+ customUserMenu={<CustomUserMenu />}
222
+ />
223
+ </TestWrapper>
224
+ );
225
+
226
+ const endTime = performance.now();
227
+ const renderTime = endTime - startTime;
228
+
229
+ expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
230
+ expect(screen.getByTestId('custom-logo')).toBeInTheDocument();
231
+ expect(screen.getByTestId('custom-user-menu')).toBeInTheDocument();
232
+ });
233
+
234
+ it('renders with large navigation items within threshold', () => {
235
+ const largeNavItems = Array.from({ length: 50 }, (_, i) => ({
236
+ id: `nav-${i}`,
237
+ label: `Navigation ${i}`,
238
+ href: `/nav-${i}`
239
+ }));
240
+
241
+ const startTime = performance.now();
242
+
243
+ render(
244
+ <TestWrapper>
245
+ <PaceAppLayout appName="Test App" navItems={largeNavItems} />
246
+ </TestWrapper>
247
+ );
248
+
249
+ const endTime = performance.now();
250
+ const renderTime = endTime - startTime;
251
+
252
+ expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
253
+ expect(screen.getByTestId('mock-header')).toBeInTheDocument();
254
+ });
255
+ });
256
+
257
+ describe('Permission Check Performance', () => {
258
+ it('performs permission checks within threshold', async () => {
259
+ const startTime = performance.now();
260
+
261
+ render(
262
+ <TestWrapper>
263
+ <PaceAppLayout appName="Test App" enforcePermissions={true} />
264
+ </TestWrapper>
265
+ );
266
+
267
+ await new Promise(resolve => setTimeout(resolve, 10)); // Wait for async operations
268
+
269
+ const endTime = performance.now();
270
+ const permissionCheckTime = endTime - startTime;
271
+
272
+ expect(permissionCheckTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
273
+ // Performance test - just verify the component renders within threshold
274
+ expect(screen.getByTestId('mock-header')).toBeInTheDocument();
275
+ });
276
+
277
+ it('handles multiple permission checks efficiently', async () => {
278
+ const routePermissions: Record<string, Operation> = {
279
+ '/dashboard': 'read',
280
+ '/settings': 'update',
281
+ '/admin': 'delete'
282
+ };
283
+
284
+ const startTime = performance.now();
285
+
286
+ render(
287
+ <TestWrapper>
288
+ <PaceAppLayout
289
+ appName="Test App"
290
+ enforcePermissions={true}
291
+ routePermissions={routePermissions}
292
+ />
293
+ </TestWrapper>
294
+ );
295
+
296
+ await new Promise(resolve => setTimeout(resolve, 10));
297
+
298
+ const endTime = performance.now();
299
+ const permissionCheckTime = endTime - startTime;
300
+
301
+ expect(permissionCheckTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
302
+ // Performance test - just verify the component renders within threshold
303
+ expect(screen.getByTestId('mock-header')).toBeInTheDocument();
304
+ });
305
+
306
+ it('handles permission check errors efficiently', async () => {
307
+ mockCheckPermission.mockRejectedValue(new Error('Permission check failed'));
308
+
309
+ const startTime = performance.now();
310
+
311
+ render(
312
+ <TestWrapper>
313
+ <PaceAppLayout appName="Test App" enforcePermissions={true} />
314
+ </TestWrapper>
315
+ );
316
+
317
+ await new Promise(resolve => setTimeout(resolve, 10));
318
+
319
+ const endTime = performance.now();
320
+ const permissionCheckTime = endTime - startTime;
321
+
322
+ expect(permissionCheckTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
323
+ });
324
+ });
325
+
326
+ describe('Memory Usage', () => {
327
+ it('does not cause significant memory leaks on re-renders', () => {
328
+ const initialMemory = (performance as any).memory?.usedJSHeapSize || 0;
329
+
330
+ const { rerender } = render(
331
+ <TestWrapper>
332
+ <PaceAppLayout appName="Test App" />
333
+ </TestWrapper>
334
+ );
335
+
336
+ // Perform multiple re-renders
337
+ for (let i = 0; i < PERFORMANCE_THRESHOLDS.RE_RENDER_COUNT; i++) {
338
+ rerender(
339
+ <TestWrapper>
340
+ <PaceAppLayout appName={`Test App ${i}`} />
341
+ </TestWrapper>
342
+ );
343
+ }
344
+
345
+ const finalMemory = (performance as any).memory?.usedJSHeapSize || 0;
346
+ const memoryIncrease = finalMemory - initialMemory;
347
+
348
+ if (initialMemory > 0 && finalMemory > 0) {
349
+ expect(memoryIncrease).toBeLessThan(PERFORMANCE_THRESHOLDS.MEMORY_USAGE_INCREASE);
350
+ }
351
+ });
352
+
353
+ it('handles large navigation items without memory issues', () => {
354
+ const largeNavItems = Array.from({ length: 100 }, (_, i) => ({
355
+ id: `nav-${i}`,
356
+ label: `Navigation ${i}`,
357
+ href: `/nav-${i}`
358
+ }));
359
+
360
+ const initialMemory = (performance as any).memory?.usedJSHeapSize || 0;
361
+
362
+ render(
363
+ <TestWrapper>
364
+ <PaceAppLayout appName="Test App" navItems={largeNavItems} />
365
+ </TestWrapper>
366
+ );
367
+
368
+ const finalMemory = (performance as any).memory?.usedJSHeapSize || 0;
369
+ const memoryIncrease = finalMemory - initialMemory;
370
+
371
+ if (initialMemory > 0 && finalMemory > 0) {
372
+ expect(memoryIncrease).toBeLessThan(PERFORMANCE_THRESHOLDS.MEMORY_USAGE_INCREASE);
373
+ }
374
+ });
375
+ });
376
+
377
+ describe('Re-render Performance', () => {
378
+ it('handles prop changes efficiently', () => {
379
+ const { rerender } = render(
380
+ <TestWrapper>
381
+ <PaceAppLayout appName="Test App" />
382
+ </TestWrapper>
383
+ );
384
+
385
+ const startTime = performance.now();
386
+
387
+ // Change props multiple times
388
+ for (let i = 0; i < PERFORMANCE_THRESHOLDS.RE_RENDER_COUNT; i++) {
389
+ rerender(
390
+ <TestWrapper>
391
+ <PaceAppLayout
392
+ appName={`Test App ${i}`}
393
+ showEventSelector={i % 2 === 0}
394
+ showUserMenu={i % 2 === 1}
395
+ />
396
+ </TestWrapper>
397
+ );
398
+ }
399
+
400
+ const endTime = performance.now();
401
+ const totalTime = endTime - startTime;
402
+ const averageTime = totalTime / PERFORMANCE_THRESHOLDS.RE_RENDER_COUNT;
403
+
404
+ expect(averageTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
405
+ });
406
+
407
+ it('handles permission enforcement toggles efficiently', async () => {
408
+ const { rerender } = render(
409
+ <TestWrapper>
410
+ <PaceAppLayout appName="Test App" />
411
+ </TestWrapper>
412
+ );
413
+
414
+ const startTime = performance.now();
415
+
416
+ // Toggle permission enforcement
417
+ rerender(
418
+ <TestWrapper>
419
+ <PaceAppLayout appName="Test App" enforcePermissions={true} />
420
+ </TestWrapper>
421
+ );
422
+
423
+ await new Promise(resolve => setTimeout(resolve, 10));
424
+
425
+ rerender(
426
+ <TestWrapper>
427
+ <PaceAppLayout appName="Test App" enforcePermissions={false} />
428
+ </TestWrapper>
429
+ );
430
+
431
+ const endTime = performance.now();
432
+ const totalTime = endTime - startTime;
433
+
434
+ expect(totalTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
435
+ });
436
+ });
437
+
438
+ describe('Navigation Performance', () => {
439
+ it('handles rapid navigation efficiently', () => {
440
+ const navItems = [
441
+ { id: 'home', label: 'Home', href: '/' },
442
+ { id: 'dashboard', label: 'Dashboard', href: '/dashboard' },
443
+ { id: 'settings', label: 'Settings', href: '/settings' },
444
+ { id: 'ui-showcase', label: 'UI Showcase', href: '/ui-showcase' },
445
+ { id: 'data-table-showcase', label: 'DataTable Showcase', href: '/data-table-showcase' }
446
+ ];
447
+
448
+ const startTime = performance.now();
449
+
450
+ render(
451
+ <TestWrapper>
452
+ <PaceAppLayout appName="Test App" navItems={navItems} enforcePermissions={false} />
453
+ </TestWrapper>
454
+ );
455
+
456
+ const endTime = performance.now();
457
+ const renderTime = endTime - startTime;
458
+
459
+ expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
460
+
461
+ // Rapidly click navigation buttons
462
+ // If not, this test will fail.
463
+ // @ts-ignore
464
+ fireEvent.click(screen.getByTestId('nav-nav-0'));
465
+ // @ts-ignore
466
+ fireEvent.click(screen.getByTestId('nav-nav-1'));
467
+ // @ts-ignore
468
+ fireEvent.click(screen.getByTestId('nav-nav-2'));
469
+
470
+ // Should handle rapid navigation without performance issues
471
+ expect(mockNavigate).toHaveBeenCalledTimes(3);
472
+ });
473
+
474
+ it('handles large navigation items efficiently', () => {
475
+ const largeNavItems = Array.from({ length: 50 }, (_, i) => ({
476
+ id: `nav-${i}`,
477
+ label: `Navigation ${i}`,
478
+ href: `/nav-${i}`
479
+ }));
480
+
481
+ render(
482
+ <TestWrapper>
483
+ <PaceAppLayout appName="Test App" navItems={largeNavItems} />
484
+ </TestWrapper>
485
+ );
486
+
487
+ const startTime = performance.now();
488
+
489
+ // Click on multiple navigation items
490
+ for (let i = 0; i < 5; i++) {
491
+ const navButton = screen.getByTestId(`nav-nav-${i}`);
492
+ // @ts-ignore
493
+ fireEvent.click(navButton);
494
+ }
495
+
496
+ const endTime = performance.now();
497
+ const navigationTime = endTime - startTime;
498
+
499
+ expect(navigationTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
500
+ });
501
+ });
502
+
503
+ describe('Authentication Performance', () => {
504
+ it('handles authentication actions efficiently', async () => {
505
+ // Reset mocks before test
506
+ mockSignOut.mockClear();
507
+ mockUpdatePassword.mockClear();
508
+
509
+ render(
510
+ <TestWrapper>
511
+ <PaceAppLayout appName="Test App" />
512
+ </TestWrapper>
513
+ );
514
+
515
+ const startTime = performance.now();
516
+
517
+ // Perform authentication actions
518
+ const signOutButton = screen.getByTestId('sign-out-button');
519
+ const changePasswordButton = screen.getByTestId('change-password-button');
520
+
521
+ fireEvent.click(signOutButton);
522
+ fireEvent.click(changePasswordButton);
523
+
524
+ // Wait for async operations to complete
525
+ await new Promise(resolve => setTimeout(resolve, 50));
526
+
527
+ const endTime = performance.now();
528
+ const authTime = endTime - startTime;
529
+
530
+ expect(authTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
531
+ expect(mockSignOut).toHaveBeenCalled();
532
+ expect(mockUpdatePassword).toHaveBeenCalledWith('newpassword123');
533
+ });
534
+
535
+ it('handles authentication errors efficiently', async () => {
536
+ // Reset mocks and set up error responses
537
+ mockSignOut.mockClear();
538
+ mockUpdatePassword.mockClear();
539
+ mockSignOut.mockResolvedValue({ error: { message: 'Sign out failed' } });
540
+ mockUpdatePassword.mockResolvedValue({ error: { message: 'Password update failed' } });
541
+
542
+ render(
543
+ <TestWrapper>
544
+ <PaceAppLayout appName="Test App" />
545
+ </TestWrapper>
546
+ );
547
+
548
+ const startTime = performance.now();
549
+
550
+ // Perform authentication actions that will fail
551
+ const signOutButton = screen.getByTestId('sign-out-button');
552
+ const changePasswordButton = screen.getByTestId('change-password-button');
553
+
554
+ fireEvent.click(signOutButton);
555
+ fireEvent.click(changePasswordButton);
556
+
557
+ // Wait for async operations to complete
558
+ await new Promise(resolve => setTimeout(resolve, 50));
559
+
560
+ const endTime = performance.now();
561
+ const authTime = endTime - startTime;
562
+
563
+ expect(authTime).toBeLessThan(PERFORMANCE_THRESHOLDS.PERMISSION_CHECK_TIME);
564
+ });
565
+ });
566
+
567
+ describe('Complex Configuration Performance', () => {
568
+ it('handles complex configurations efficiently', () => {
569
+ const customNavItems = Array.from({ length: 20 }, (_, i) => ({
570
+ id: `nav-${i}`,
571
+ label: `Navigation ${i}`,
572
+ href: `/nav-${i}`
573
+ }));
574
+
575
+ const HeaderActions = () => (
576
+ <div data-testid="header-actions">
577
+ {Array.from({ length: 5 }, (_, i) => (
578
+ <button key={i} data-testid={`action-${i}`}>Action {i}</button>
579
+ ))}
580
+ </div>
581
+ );
582
+
583
+ const CustomUserMenu = () => (
584
+ <div data-testid="custom-user-menu">
585
+ {Array.from({ length: 3 }, (_, i) => (
586
+ <button key={i} data-testid={`menu-${i}`}>Menu {i}</button>
587
+ ))}
588
+ </div>
589
+ );
590
+
591
+ const startTime = performance.now();
592
+
593
+ render(
594
+ <TestWrapper>
595
+ <PaceAppLayout
596
+ appName="Complex App"
597
+ navItems={customNavItems}
598
+ headerActions={<HeaderActions />}
599
+ customUserMenu={<CustomUserMenu />}
600
+ showEventSelector={false}
601
+ showUserMenu={true}
602
+ headerClassName="complex-header-class"
603
+ enforcePermissions={false}
604
+ defaultPermission="update"
605
+ routePermissions={{
606
+ '/nav-0': 'read',
607
+ '/nav-1': 'update',
608
+ '/nav-2': 'delete'
609
+ }}
610
+ pageIdMapping={{
611
+ '/nav-0': 'page-0',
612
+ '/nav-1': 'page-1',
613
+ '/nav-2': 'page-2'
614
+ }}
615
+ filterNavigationByPermissions={true}
616
+ />
617
+ </TestWrapper>
618
+ );
619
+
620
+ const endTime = performance.now();
621
+ const renderTime = endTime - startTime;
622
+
623
+ expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.RENDER_TIME);
624
+ expect(screen.getByTestId('header-actions')).toBeInTheDocument();
625
+ expect(screen.getByTestId('custom-user-menu')).toBeInTheDocument();
626
+ });
627
+ });
628
+ });