@jmruthers/pace-core 0.6.6 → 0.6.8
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/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +227 -22
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +240 -0
- package/audit-tool/audits/03-architecture.cjs +224 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +554 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +355 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +295 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +380 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
- package/cursor-rules/02-project-structure.mdc +37 -5
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
- package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/DataTable-6RMSCQJ6.js +15 -0
- package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-CIGSujI2.d.ts} +40 -24
- package/dist/{UnifiedAuthProvider-ZT6TIGM7.js → UnifiedAuthProvider-7SNDOWYD.js} +2 -2
- package/dist/{api-Y4MQWOFW.js → api-7P7DI652.js} +1 -1
- package/dist/{chunk-MAGBIDNS.js → chunk-4DDCYDQ3.js} +8 -7
- package/dist/{chunk-BVP2BCJF.js → chunk-5W2A3DRC.js} +10 -9
- package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
- package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
- package/dist/{chunk-3O3WHILE.js → chunk-EF2UGZWY.js} +239 -63
- package/dist/{chunk-LAZMKTTF.js → chunk-EURB7QFZ.js} +341 -337
- package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
- package/dist/{chunk-7TYHROIV.js → chunk-GS5672WG.js} +55 -13
- package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
- package/dist/{chunk-ZFYPMX46.js → chunk-LX6U42O3.js} +1 -1
- package/dist/{chunk-FENMYN2U.js → chunk-MPBLMWVR.js} +3 -3
- package/dist/{chunk-ZS5VO5JB.js → chunk-NKHKXPI4.js} +408 -453
- package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
- package/dist/{chunk-4T7OBVTU.js → chunk-S6ZQKDY6.js} +1 -1
- package/dist/{chunk-FTCRZOG2.js → chunk-T5CVK4R3.js} +5 -5
- package/dist/{chunk-OHIK3MIO.js → chunk-Z2FNRKF3.js} +13 -13
- package/dist/components.d.ts +5 -4
- package/dist/components.js +29 -34
- package/dist/eslint-rules/index.cjs +22 -9
- package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
- package/dist/eslint-rules/rules/04-code-quality.cjs +346 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +34 -13
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +385 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/{functions-DHebl8-F.d.ts → functions-lBy5L2ry.d.ts} +1 -1
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -8
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -20
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +8 -8
- package/dist/theming/runtime.d.ts +61 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +8 -8
- package/docs/README.md +1 -1
- package/docs/api/modules.md +106 -41
- package/docs/api-reference/components.md +18 -20
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/dependencies.md +23 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/implementation-guides/app-layout.md +1 -1
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +127 -34
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +1 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/README.md +5 -5
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -1
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +241 -185
- package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
- package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +203 -104
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/eslint-config-pace-core.cjs +50 -20
- package/package.json +50 -19
- package/scripts/eslint-audit.cjs +123 -0
- package/scripts/install-cursor-rules.cjs +11 -243
- package/scripts/install-eslint-config.cjs +349 -0
- package/scripts/validate-dependencies.cjs +248 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +30 -18
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +10 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/AddressField/AddressField.tsx +26 -1
- package/src/components/Alert/Alert.test.tsx +86 -22
- package/src/components/Alert/Alert.tsx +19 -11
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/Checkbox/Checkbox.test.tsx +2 -1
- package/src/components/ContextSelector/ContextSelector.tsx +108 -126
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/DataTable.tsx +1 -19
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +6 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +21 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +3 -2
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
- package/src/components/DataTable/components/EditableRow.tsx +5 -7
- package/src/components/DataTable/components/EmptyState.tsx +11 -10
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +45 -27
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/types.ts +0 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +7 -8
- package/src/components/Dialog/Dialog.test.tsx +1 -0
- package/src/components/Dialog/Dialog.tsx +25 -8
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileUpload/FileUpload.test.tsx +45 -16
- package/src/components/FileUpload/FileUpload.tsx +141 -130
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +48 -12
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +30 -30
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +7 -1
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +86 -77
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +8 -5
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +4 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +99 -99
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +45 -8
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +22 -2
- package/src/hooks/public/usePublicEvent.ts +5 -5
- package/src/hooks/public/usePublicEventLogo.ts +5 -5
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +13 -9
- package/src/hooks/useAddressAutocomplete.test.ts +18 -18
- package/src/hooks/useAppConfig.ts +2 -2
- package/src/hooks/useEventTheme.test.ts +7 -7
- package/src/hooks/useEventTheme.ts +2 -1
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useFileUrl.ts +52 -8
- package/src/hooks/useOrganisationSecurity.test.ts +2 -1
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +38 -38
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +15 -6
- package/src/rbac/__tests__/rbac-functions.test.ts +3 -3
- package/src/rbac/api.test.ts +104 -0
- package/src/rbac/engine.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +2 -2
- package/src/rbac/secureClient.ts +1 -1
- package/src/rbac/types/functions.ts +1 -1
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +118 -3
- package/src/theming/parseEventColours.ts +77 -11
- package/src/types/supabase.ts +2 -3
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +9 -9
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/file-reference/__tests__/file-reference.test.ts +4 -0
- package/src/utils/formatting/formatDate.test.ts +3 -2
- package/src/utils/formatting/formatDateTime.test.ts +2 -2
- package/src/utils/google-places/googlePlacesUtils.test.ts +36 -24
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/__tests__/helpers.unit.test.ts +19 -12
- package/src/utils/storage/helpers.test.ts +69 -3
- package/cursor-rules/01-standards-compliance.mdc +0 -285
- package/cursor-rules/04-testing-standards.mdc +0 -270
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
- package/cursor-rules/06-code-quality.mdc +0 -311
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
- package/cursor-rules/10-error-handling-patterns.mdc +0 -179
- package/cursor-rules/11-performance-optimization.mdc +0 -169
- package/cursor-rules/12-ci-cd-integration.mdc +0 -150
- package/dist/DataTable-LRJL4IRV.js +0 -15
- package/dist/eslint-rules/rules/compliance.cjs +0 -348
- package/dist/eslint-rules/rules/components.cjs +0 -113
- package/dist/eslint-rules/rules/imports.cjs +0 -102
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -604
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-standards-compliance.md +0 -188
- package/docs/standards/03-solid-principles.md +0 -39
- package/docs/standards/04-testing-standards.md +0 -36
- package/docs/standards/05-bug-reports-and-features.md +0 -27
- package/docs/standards/06-code-quality.md +0 -34
- package/docs/standards/07-tech-stack-compliance.md +0 -30
- package/docs/standards/10-error-handling-patterns.md +0 -401
- package/docs/standards/11-performance-optimization.md +0 -348
- package/docs/standards/12-ci-cd-integration.md +0 -370
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
- package/scripts/audit/audit-compliance.cjs +0 -1295
- package/scripts/audit/audit-components.cjs +0 -260
- package/scripts/audit/audit-rbac.cjs +0 -954
- package/scripts/audit/audit-standards.cjs +0 -1268
- package/scripts/audit/index.cjs +0 -1927
- package/src/components/DataTable/components/DataTableBody.tsx +0 -478
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/index.cjs +0 -22
- package/src/eslint-rules/rules/components.cjs +0 -113
- package/src/eslint-rules/rules/imports.cjs +0 -102
- package/src/eslint-rules/rules/rbac.cjs +0 -790
- package/src/eslint-rules/utils/helpers.cjs +0 -42
- package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
|
@@ -288,15 +288,29 @@ describe('useInactivityTracker', () => {
|
|
|
288
288
|
});
|
|
289
289
|
|
|
290
290
|
it('broadcasts activity to other tabs', () => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
// Ensure no persisted time to force resetActivity call
|
|
292
|
+
localStorageMock.getItem.mockReturnValue(null);
|
|
293
|
+
|
|
294
|
+
// Start with enabled: true to allow tracking
|
|
295
|
+
const { result } = renderHook(
|
|
296
|
+
({ enabled }) => useInactivityTracker({
|
|
297
|
+
enabled,
|
|
294
298
|
channelName: 'test-channel'
|
|
295
|
-
})
|
|
299
|
+
}),
|
|
300
|
+
{ initialProps: { enabled: true } }
|
|
296
301
|
);
|
|
297
302
|
|
|
303
|
+
// Wait for automatic initialization
|
|
298
304
|
act(() => {
|
|
299
|
-
|
|
305
|
+
vi.advanceTimersByTime(0);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Clear any existing calls from initialization
|
|
309
|
+
mockBroadcastChannel.postMessage.mockClear();
|
|
310
|
+
|
|
311
|
+
// Trigger activity reset which should broadcast
|
|
312
|
+
act(() => {
|
|
313
|
+
result.current.resetActivity();
|
|
300
314
|
});
|
|
301
315
|
|
|
302
316
|
expect(mockBroadcastChannel.postMessage).toHaveBeenCalledWith({
|
|
@@ -337,16 +351,39 @@ describe('useInactivityTracker', () => {
|
|
|
337
351
|
|
|
338
352
|
it('cleans up timers and listeners on unmount', () => {
|
|
339
353
|
const { result, unmount } = renderHook(() =>
|
|
340
|
-
useInactivityTracker({ enabled: true })
|
|
354
|
+
useInactivityTracker({ enabled: true, channelName: 'test-channel' })
|
|
341
355
|
);
|
|
342
356
|
|
|
357
|
+
// Wait for automatic initialization
|
|
343
358
|
act(() => {
|
|
344
|
-
|
|
359
|
+
vi.advanceTimersByTime(0);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Ensure tracking is active and channel/listeners are set up
|
|
363
|
+
expect(result.current.isTracking).toBe(true);
|
|
364
|
+
expect(mockAddEventListener).toHaveBeenCalled();
|
|
365
|
+
|
|
366
|
+
// Verify channel was created
|
|
367
|
+
expect(window.BroadcastChannel).toHaveBeenCalled();
|
|
368
|
+
|
|
369
|
+
// Clear mocks to track cleanup calls
|
|
370
|
+
mockRemoveEventListener.mockClear();
|
|
371
|
+
mockBroadcastChannel.close.mockClear();
|
|
372
|
+
|
|
373
|
+
// Unmount should trigger cleanup
|
|
374
|
+
act(() => {
|
|
375
|
+
unmount();
|
|
345
376
|
});
|
|
346
377
|
|
|
347
|
-
|
|
378
|
+
// Advance timers to ensure cleanup completes
|
|
379
|
+
act(() => {
|
|
380
|
+
vi.advanceTimersByTime(100);
|
|
381
|
+
});
|
|
348
382
|
|
|
383
|
+
// Verify cleanup was called
|
|
384
|
+
// The hook should remove all event listeners that were added
|
|
349
385
|
expect(mockRemoveEventListener).toHaveBeenCalled();
|
|
386
|
+
// Channel cleanup happens in the unmount effect
|
|
350
387
|
expect(mockBroadcastChannel.close).toHaveBeenCalled();
|
|
351
388
|
});
|
|
352
389
|
|
|
@@ -610,7 +610,13 @@ describe('useOperationPerformance', () => {
|
|
|
610
610
|
useOperationPerformance('testOperation', 'OPERATION')
|
|
611
611
|
);
|
|
612
612
|
|
|
613
|
-
|
|
613
|
+
// Clear any previous calls
|
|
614
|
+
vi.clearAllMocks();
|
|
615
|
+
|
|
616
|
+
// Use mockReturnValueOnce to ensure exact sequence
|
|
617
|
+
mockPerformanceNow
|
|
618
|
+
.mockReturnValueOnce(0) // Start time
|
|
619
|
+
.mockReturnValueOnce(20); // End time
|
|
614
620
|
|
|
615
621
|
const operation = () => 'result';
|
|
616
622
|
|
|
@@ -623,12 +629,22 @@ describe('useOperationPerformance', () => {
|
|
|
623
629
|
operation: 'testOperation',
|
|
624
630
|
})
|
|
625
631
|
);
|
|
632
|
+
|
|
633
|
+
// Reset mock
|
|
634
|
+
mockPerformanceNow.mockImplementation(() => mockPerformanceNowValue);
|
|
626
635
|
});
|
|
627
636
|
|
|
628
637
|
it('includes context in measurement', async () => {
|
|
629
638
|
const { result } = renderHook(() => useOperationPerformance('testOperation'));
|
|
630
639
|
|
|
631
|
-
|
|
640
|
+
// Use a sequence to ensure correct values are returned
|
|
641
|
+
let callCount = 0;
|
|
642
|
+
mockPerformanceNow.mockImplementation(() => {
|
|
643
|
+
callCount++;
|
|
644
|
+
if (callCount === 1) return 0; // Start time
|
|
645
|
+
if (callCount === 2) return 15; // End time
|
|
646
|
+
return mockPerformanceNowValue; // Fallback
|
|
647
|
+
});
|
|
632
648
|
|
|
633
649
|
const operation = () => 'result';
|
|
634
650
|
const context = { userId: '123', action: 'test' };
|
|
@@ -644,6 +660,10 @@ describe('useOperationPerformance', () => {
|
|
|
644
660
|
action: 'test',
|
|
645
661
|
})
|
|
646
662
|
);
|
|
663
|
+
|
|
664
|
+
// Reset mock
|
|
665
|
+
callCount = 0;
|
|
666
|
+
mockPerformanceNow.mockImplementation(() => mockPerformanceNowValue);
|
|
647
667
|
});
|
|
648
668
|
|
|
649
669
|
it('handles operation errors', async () => {
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
* const { eventCode } = usePublicRouteParams();
|
|
23
23
|
* const { event, isLoading, error, refetch } = usePublicEvent(eventCode);
|
|
24
24
|
*
|
|
25
|
-
* if (isLoading) return <
|
|
26
|
-
* if (error) return <
|
|
27
|
-
* if (!event) return <
|
|
25
|
+
* if (isLoading) return <p>Loading event...</p>;
|
|
26
|
+
* if (error) return <p>Error: {error.message}</p>;
|
|
27
|
+
* if (!event) return <p>Event not found</p>;
|
|
28
28
|
*
|
|
29
29
|
* return (
|
|
30
|
-
* <
|
|
30
|
+
* <section>
|
|
31
31
|
* <h1>{event.event_name}</h1>
|
|
32
32
|
* <p>Date: {event.event_date}</p>
|
|
33
33
|
* <p>Venue: {event.event_venue}</p>
|
|
34
|
-
* </
|
|
34
|
+
* </section>
|
|
35
35
|
* );
|
|
36
36
|
* }
|
|
37
37
|
* ```
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
* organisationId
|
|
27
27
|
* );
|
|
28
28
|
*
|
|
29
|
-
* if (isLoading) return <
|
|
30
|
-
* if (error) return <
|
|
29
|
+
* if (isLoading) return <p>Loading logo...</p>;
|
|
30
|
+
* if (error) return <p>Error: {error.message}</p>;
|
|
31
31
|
*
|
|
32
32
|
* return (
|
|
33
|
-
* <
|
|
33
|
+
* <section>
|
|
34
34
|
* {logoUrl ? (
|
|
35
35
|
* <img src={logoUrl} alt={`${eventName} logo`} />
|
|
36
36
|
* ) : (
|
|
37
|
-
* <
|
|
37
|
+
* <p className="logo-fallback">{fallbackText}</p>
|
|
38
38
|
* )}
|
|
39
|
-
* </
|
|
39
|
+
* </section>
|
|
40
40
|
* );
|
|
41
41
|
* }
|
|
42
42
|
* ```
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
* { supabase }
|
|
29
29
|
* );
|
|
30
30
|
*
|
|
31
|
-
* if (isLoading) return <
|
|
32
|
-
* if (error) return <
|
|
31
|
+
* if (isLoading) return <p>Loading...</p>;
|
|
32
|
+
* if (error) return <p>Error: {error.message}</p>;
|
|
33
33
|
*
|
|
34
34
|
* return fileUrl ? <img src={fileUrl} alt="File" /> : null;
|
|
35
35
|
* }
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
* function PublicEventPage() {
|
|
22
22
|
* const { eventCode, eventId, event, error, isLoading } = usePublicRouteParams();
|
|
23
23
|
*
|
|
24
|
-
* if (isLoading) return <
|
|
25
|
-
* if (error) return <
|
|
26
|
-
* if (!event) return <
|
|
24
|
+
* if (isLoading) return <p>Loading...</p>;
|
|
25
|
+
* if (error) return <p>Error: {error.message}</p>;
|
|
26
|
+
* if (!event) return <p>Event not found</p>;
|
|
27
27
|
*
|
|
28
28
|
* return (
|
|
29
|
-
* <
|
|
29
|
+
* <section>
|
|
30
30
|
* <h1>{event.event_name}</h1>
|
|
31
31
|
* <p>Event Code: {eventCode}</p>
|
|
32
|
-
* </
|
|
32
|
+
* </section>
|
|
33
33
|
* );
|
|
34
34
|
* }
|
|
35
35
|
* ```
|
|
@@ -182,16 +182,20 @@ export function usePublicRouteParams(
|
|
|
182
182
|
|
|
183
183
|
// Determine the final error state
|
|
184
184
|
const finalError = useMemo(() => {
|
|
185
|
+
// Always show validation errors (format validation)
|
|
185
186
|
if (error) return error;
|
|
186
|
-
|
|
187
|
+
// When validation is disabled, ignore event errors (invalid codes are allowed)
|
|
188
|
+
if (!validateEventCode) return null;
|
|
189
|
+
// Include eventError if we're fetching event data and validation is enabled
|
|
190
|
+
if (fetchEventData && eventError) return eventError;
|
|
187
191
|
return null;
|
|
188
|
-
}, [error, eventError]);
|
|
192
|
+
}, [error, eventError, fetchEventData, validateEventCode]);
|
|
189
193
|
|
|
190
194
|
// Extract event ID from event data
|
|
191
195
|
const eventId = useMemo(() => {
|
|
192
|
-
if (!event) return null;
|
|
196
|
+
if (!fetchEventData || !event) return null;
|
|
193
197
|
return event.event_id || event.id;
|
|
194
|
-
}, [event]);
|
|
198
|
+
}, [fetchEventData, event]);
|
|
195
199
|
|
|
196
200
|
// Refetch function
|
|
197
201
|
const refetch = useCallback(async (): Promise<void> => {
|
|
@@ -45,17 +45,17 @@ describe('useAddressAutocomplete', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
describe('Initial state', () => {
|
|
48
|
-
it('returns initial state with empty suggestions', () => {
|
|
48
|
+
it('returns initial state with empty suggestions', { timeout: 5000 }, () => {
|
|
49
49
|
const { result } = renderHook(() => useAddressAutocomplete(mockApiKey, ''));
|
|
50
50
|
|
|
51
51
|
expect(result.current.suggestions).toEqual([]);
|
|
52
52
|
expect(result.current.isLoading).toBe(false);
|
|
53
53
|
expect(result.current.error).toBeNull();
|
|
54
|
-
}
|
|
54
|
+
});
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
describe('Autocomplete suggestions', () => {
|
|
58
|
-
it('fetches suggestions when input value changes', async () => {
|
|
58
|
+
it('fetches suggestions when input value changes', { timeout: 5000 }, async () => {
|
|
59
59
|
const mockPredictions = [
|
|
60
60
|
{
|
|
61
61
|
description: '123 Main St, Melbourne VIC, Australia',
|
|
@@ -81,9 +81,9 @@ describe('useAddressAutocomplete', () => {
|
|
|
81
81
|
);
|
|
82
82
|
|
|
83
83
|
expect(result.current.suggestions[0].place_id).toBe('ChIJ123');
|
|
84
|
-
}
|
|
84
|
+
});
|
|
85
85
|
|
|
86
|
-
it('debounces input value', async () => {
|
|
86
|
+
it('debounces input value', { timeout: 5000 }, async () => {
|
|
87
87
|
vi.mocked(googlePlacesUtils.fetchPlaceAutocomplete).mockResolvedValue([]);
|
|
88
88
|
|
|
89
89
|
// Since we're mocking useDebounce to return immediately, this test verifies
|
|
@@ -108,9 +108,9 @@ describe('useAddressAutocomplete', () => {
|
|
|
108
108
|
|
|
109
109
|
// With mocked debounce, it will be called for each change
|
|
110
110
|
expect(googlePlacesUtils.fetchPlaceAutocomplete).toHaveBeenCalled();
|
|
111
|
-
}
|
|
111
|
+
});
|
|
112
112
|
|
|
113
|
-
it('clears suggestions when input is empty', async () => {
|
|
113
|
+
it('clears suggestions when input is empty', { timeout: 5000 }, async () => {
|
|
114
114
|
vi.mocked(googlePlacesUtils.fetchPlaceAutocomplete).mockResolvedValueOnce([
|
|
115
115
|
{ description: '123 Main St', place_id: 'ChIJ123' },
|
|
116
116
|
]);
|
|
@@ -137,9 +137,9 @@ describe('useAddressAutocomplete', () => {
|
|
|
137
137
|
},
|
|
138
138
|
{ timeout: 1000 }
|
|
139
139
|
);
|
|
140
|
-
}
|
|
140
|
+
});
|
|
141
141
|
|
|
142
|
-
it('handles API errors', async () => {
|
|
142
|
+
it('handles API errors', { timeout: 5000 }, async () => {
|
|
143
143
|
const error = new Error('API request denied');
|
|
144
144
|
vi.mocked(googlePlacesUtils.fetchPlaceAutocomplete).mockRejectedValueOnce(error);
|
|
145
145
|
|
|
@@ -156,11 +156,11 @@ describe('useAddressAutocomplete', () => {
|
|
|
156
156
|
|
|
157
157
|
expect(result.current.error?.message).toBe('API request denied');
|
|
158
158
|
expect(result.current.suggestions).toEqual([]);
|
|
159
|
-
}
|
|
159
|
+
});
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
describe('Address selection', () => {
|
|
163
|
-
it('selects address by place_id', async () => {
|
|
163
|
+
it('selects address by place_id', { timeout: 5000 }, async () => {
|
|
164
164
|
const mockPlaceDetails = {
|
|
165
165
|
place_id: 'ChIJ123',
|
|
166
166
|
formatted_address: '123 Main St, Melbourne VIC 3000, Australia',
|
|
@@ -191,7 +191,7 @@ describe('useAddressAutocomplete', () => {
|
|
|
191
191
|
expect(address).not.toBeNull();
|
|
192
192
|
expect(address?.place_id).toBe('ChIJ123');
|
|
193
193
|
expect(googlePlacesUtils.fetchPlaceDetails).toHaveBeenCalledWith('ChIJ123', mockApiKey);
|
|
194
|
-
}
|
|
194
|
+
});
|
|
195
195
|
|
|
196
196
|
it('returns null when place_id is invalid', async () => {
|
|
197
197
|
vi.mocked(googlePlacesUtils.fetchPlaceDetails).mockRejectedValueOnce(new Error('Place not found'));
|
|
@@ -205,7 +205,7 @@ describe('useAddressAutocomplete', () => {
|
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
describe('getAddressByPlaceId', () => {
|
|
208
|
-
it('retrieves address by place_id', async () => {
|
|
208
|
+
it('retrieves address by place_id', { timeout: 5000 }, async () => {
|
|
209
209
|
const mockAddress = {
|
|
210
210
|
place_id: 'ChIJ123',
|
|
211
211
|
full_address: '123 Main St',
|
|
@@ -227,7 +227,7 @@ describe('useAddressAutocomplete', () => {
|
|
|
227
227
|
|
|
228
228
|
expect(address).not.toBeNull();
|
|
229
229
|
expect(address?.place_id).toBe('ChIJ123');
|
|
230
|
-
}
|
|
230
|
+
});
|
|
231
231
|
|
|
232
232
|
it('returns null on error', async () => {
|
|
233
233
|
vi.mocked(googlePlacesUtils.getAddressByPlaceId).mockResolvedValueOnce(null);
|
|
@@ -237,11 +237,11 @@ describe('useAddressAutocomplete', () => {
|
|
|
237
237
|
const address = await result.current.getAddressByPlaceId('invalid');
|
|
238
238
|
|
|
239
239
|
expect(address).toBeNull();
|
|
240
|
-
}
|
|
240
|
+
});
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
describe('clearSuggestions', () => {
|
|
244
|
-
it('clears suggestions', async () => {
|
|
244
|
+
it('clears suggestions', { timeout: 5000 }, async () => {
|
|
245
245
|
vi.mocked(googlePlacesUtils.fetchPlaceAutocomplete).mockResolvedValueOnce([
|
|
246
246
|
{ description: '123 Main St', place_id: 'ChIJ123' },
|
|
247
247
|
]);
|
|
@@ -268,7 +268,7 @@ describe('useAddressAutocomplete', () => {
|
|
|
268
268
|
},
|
|
269
269
|
{ timeout: 1000 }
|
|
270
270
|
);
|
|
271
|
-
}
|
|
271
|
+
});
|
|
272
272
|
});
|
|
273
273
|
|
|
274
274
|
describe('Caching', () => {
|
|
@@ -312,7 +312,7 @@ describe('useAddressAutocomplete', () => {
|
|
|
312
312
|
);
|
|
313
313
|
|
|
314
314
|
expect(googlePlacesUtils.fetchPlaceAutocomplete).toHaveBeenCalled();
|
|
315
|
-
}
|
|
315
|
+
});
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
318
|
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* function MyComponent() {
|
|
15
15
|
* const { appName, isLoading } = useAppConfig();
|
|
16
16
|
*
|
|
17
|
-
* if (isLoading) return <
|
|
17
|
+
* if (isLoading) return <p>Loading...</p>;
|
|
18
18
|
*
|
|
19
|
-
* return <
|
|
19
|
+
* return <p>App: {appName}</p>;
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
@@ -283,13 +283,13 @@ describe('useEventTheme', () => {
|
|
|
283
283
|
throw new Error('Palette application failed');
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
);
|
|
286
|
+
// The hook should not throw - errors are caught silently
|
|
287
|
+
expect(() => {
|
|
288
|
+
renderHook(() => useEventTheme(), { wrapper });
|
|
289
|
+
}).not.toThrow();
|
|
290
|
+
|
|
291
|
+
// Verify that applyPalette was called (even though it threw)
|
|
292
|
+
expect(mockApplyPalette).toHaveBeenCalled();
|
|
293
293
|
});
|
|
294
294
|
});
|
|
295
295
|
|
|
@@ -43,8 +43,8 @@ import { useLocation } from 'react-router-dom';
|
|
|
43
43
|
import { EventServiceContext } from '../providers/services/EventServiceProvider';
|
|
44
44
|
import { applyPalette, clearPalette } from '../theming/runtime';
|
|
45
45
|
import { parseAndNormalizeEventColours } from '../theming/parseEventColours';
|
|
46
|
-
import type { Event } from '../types/event';
|
|
47
46
|
import { createLogger } from '../utils/core/logger';
|
|
47
|
+
import type { Event } from '../types/event';
|
|
48
48
|
|
|
49
49
|
const log = createLogger('useEventTheme');
|
|
50
50
|
|
|
@@ -147,6 +147,7 @@ export function useEventTheme(event?: Event | null): void {
|
|
|
147
147
|
try {
|
|
148
148
|
applyPalette(normalized);
|
|
149
149
|
} catch (error) {
|
|
150
|
+
// Log error but don't throw - theming is not critical
|
|
150
151
|
log.error('Failed to apply event palette:', error);
|
|
151
152
|
}
|
|
152
153
|
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* FileCategory.EVENT_LOGOS
|
|
27
27
|
* );
|
|
28
28
|
*
|
|
29
|
-
* if (isLoading) return <
|
|
30
|
-
* if (error) return <
|
|
29
|
+
* if (isLoading) return <p>Loading...</p>;
|
|
30
|
+
* if (error) return <p>Error: {error.message}</p>;
|
|
31
31
|
*
|
|
32
32
|
* return fileUrl ? <img src={fileUrl} alt="File" /> : null;
|
|
33
33
|
* }
|
package/src/hooks/useFileUrl.ts
CHANGED
|
@@ -57,12 +57,21 @@ export function useFileUrl(
|
|
|
57
57
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
58
58
|
const [error, setError] = useState<Error | null>(null);
|
|
59
59
|
const fileReferenceIdRef = useRef<string | null>(null);
|
|
60
|
+
const isLoadingRef = useRef<boolean>(false);
|
|
61
|
+
const urlRef = useRef<string | null>(null);
|
|
62
|
+
|
|
63
|
+
// Keep refs in sync with state
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
isLoadingRef.current = isLoading;
|
|
66
|
+
urlRef.current = url;
|
|
67
|
+
}, [isLoading, url]);
|
|
60
68
|
|
|
61
69
|
const loadUrl = useCallback(async () => {
|
|
62
70
|
if (!fileReference) {
|
|
63
71
|
setUrl(null);
|
|
64
72
|
setIsLoading(false);
|
|
65
73
|
setError(null);
|
|
74
|
+
fileReferenceIdRef.current = null;
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -70,11 +79,12 @@ export function useFileUrl(
|
|
|
70
79
|
setUrl(null);
|
|
71
80
|
setIsLoading(false);
|
|
72
81
|
setError(new Error('Supabase client is required for URL generation'));
|
|
82
|
+
fileReferenceIdRef.current = null;
|
|
73
83
|
return;
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
// Skip if already loading or URL already exists for this file
|
|
77
|
-
if (
|
|
87
|
+
if (isLoadingRef.current || (urlRef.current && fileReferenceIdRef.current === fileReference.id)) {
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
80
90
|
|
|
@@ -108,29 +118,63 @@ export function useFileUrl(
|
|
|
108
118
|
} finally {
|
|
109
119
|
setIsLoading(false);
|
|
110
120
|
}
|
|
111
|
-
}, [fileReference, supabase, organisation_id
|
|
121
|
+
}, [fileReference, supabase, organisation_id]);
|
|
112
122
|
|
|
113
123
|
const clear = useCallback(() => {
|
|
114
124
|
setUrl(null);
|
|
115
125
|
setError(null);
|
|
116
126
|
setIsLoading(false);
|
|
117
127
|
fileReferenceIdRef.current = null;
|
|
128
|
+
urlRef.current = null;
|
|
118
129
|
}, []);
|
|
119
130
|
|
|
120
131
|
// Auto-load URL when fileReference changes
|
|
121
132
|
useEffect(() => {
|
|
122
|
-
if (autoLoad) {
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
if (!autoLoad) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const currentFileId = fileReference?.id ?? null;
|
|
138
|
+
const previousFileId = fileReferenceIdRef.current;
|
|
139
|
+
|
|
140
|
+
// Handle null file reference
|
|
141
|
+
if (!fileReference) {
|
|
142
|
+
if (previousFileId !== null) {
|
|
125
143
|
setUrl(null);
|
|
126
144
|
setError(null);
|
|
145
|
+
fileReferenceIdRef.current = null;
|
|
146
|
+
urlRef.current = null;
|
|
127
147
|
}
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Reset URL when file reference ID changes (but not on initial mount)
|
|
152
|
+
if (previousFileId !== null && previousFileId !== currentFileId) {
|
|
153
|
+
setUrl(null);
|
|
154
|
+
setError(null);
|
|
155
|
+
fileReferenceIdRef.current = null;
|
|
156
|
+
urlRef.current = null; // Also reset the ref immediately
|
|
157
|
+
// Immediately try to load - loadUrl will check if already loading
|
|
158
|
+
loadUrl();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Initial load or reload after clear: Load URL if we have a file reference and no URL is set
|
|
163
|
+
if (previousFileId === null && currentFileId !== null) {
|
|
164
|
+
// First time loading this file or reloading after clear - update ref and load
|
|
165
|
+
fileReferenceIdRef.current = currentFileId;
|
|
166
|
+
if (!isLoading && !url) {
|
|
130
167
|
loadUrl();
|
|
131
168
|
}
|
|
169
|
+
} else if (previousFileId === currentFileId && !url && !isLoading) {
|
|
170
|
+
// Same file but URL not loaded yet (e.g., after clear or initial mount edge case)
|
|
171
|
+
// Ensure ref is set correctly
|
|
172
|
+
if (fileReferenceIdRef.current !== currentFileId) {
|
|
173
|
+
fileReferenceIdRef.current = currentFileId;
|
|
174
|
+
}
|
|
175
|
+
loadUrl();
|
|
132
176
|
}
|
|
133
|
-
}, [fileReference
|
|
177
|
+
}, [fileReference, autoLoad, loadUrl, url, isLoading]);
|
|
134
178
|
|
|
135
179
|
return {
|
|
136
180
|
url,
|
|
@@ -625,7 +625,8 @@ describe('useOrganisationSecurity', () => {
|
|
|
625
625
|
|
|
626
626
|
it('handles permission retrieval errors gracefully', async () => {
|
|
627
627
|
mockIsSuperAdmin.mockResolvedValue(false);
|
|
628
|
-
|
|
628
|
+
// getUserPermissions uses getPermissionMap, not isPermittedCached
|
|
629
|
+
mockGetPermissionMap.mockRejectedValue(new Error('Permission retrieval failed'));
|
|
629
630
|
|
|
630
631
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
631
632
|
|