@jmruthers/pace-core 0.6.5 → 0.6.7
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/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -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 +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -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 +291 -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 +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- 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/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- 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/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -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/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- 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 +5 -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/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- 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 -35
- 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/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- 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 +185 -57
- 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/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- 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/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- 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/RowComponent.tsx +12 -0
- 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 +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- 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 +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- 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-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- 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/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -14,42 +14,23 @@ vi.mock('../../components/PublicLayout/PublicPageProvider', () => ({
|
|
|
14
14
|
}))
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
-
// Mock Supabase client
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
select: vi.fn(() => ({
|
|
22
|
-
eq: vi.fn(() => ({
|
|
23
|
-
eq: vi.fn(() => ({
|
|
24
|
-
not: vi.fn(() => ({
|
|
25
|
-
limit: vi.fn(() => ({
|
|
26
|
-
single: vi.fn()
|
|
27
|
-
}))
|
|
28
|
-
}))
|
|
29
|
-
}))
|
|
30
|
-
}))
|
|
31
|
-
}))
|
|
32
|
-
}))
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Helper to create table query mocks
|
|
36
|
-
const createTableQueryMock = () => ({
|
|
37
|
-
data: null,
|
|
38
|
-
error: null,
|
|
39
|
-
mockResolvedValueOnce: vi.fn(),
|
|
40
|
-
mockResolvedValue: vi.fn()
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// Helper to create a complete table query chain
|
|
44
|
-
const createTableQueryChain = (finalResult: any) => {
|
|
45
|
-
const mockChain = {
|
|
17
|
+
// Mock Supabase client with proper query builder chain
|
|
18
|
+
// usePublicEvent uses: .from('core_events').select(...).eq(...).eq(...).not(...).limit(1).single()
|
|
19
|
+
const createMockQueryBuilder = (mockData: any = null, mockError: any = null) => {
|
|
20
|
+
const builder: any = {
|
|
46
21
|
select: vi.fn().mockReturnThis(),
|
|
47
22
|
eq: vi.fn().mockReturnThis(),
|
|
48
23
|
not: vi.fn().mockReturnThis(),
|
|
49
24
|
limit: vi.fn().mockReturnThis(),
|
|
50
|
-
single: vi.fn().mockResolvedValue(
|
|
25
|
+
single: vi.fn().mockResolvedValue({ data: mockData, error: mockError }),
|
|
26
|
+
maybeSingle: vi.fn().mockResolvedValue({ data: mockData, error: mockError })
|
|
51
27
|
};
|
|
52
|
-
return
|
|
28
|
+
return builder;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const mockSupabaseClient = {
|
|
32
|
+
rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
33
|
+
from: vi.fn(() => createMockQueryBuilder())
|
|
53
34
|
};
|
|
54
35
|
|
|
55
36
|
// Mock createClient
|
|
@@ -110,7 +91,7 @@ describe('usePublicEvent', () => {
|
|
|
110
91
|
expect(result.current.error).toBe(null);
|
|
111
92
|
});
|
|
112
93
|
|
|
113
|
-
it('should fetch event data successfully via
|
|
94
|
+
it('should fetch event data successfully via table query', async () => {
|
|
114
95
|
const mockEventData = {
|
|
115
96
|
event_id: '123',
|
|
116
97
|
event_name: 'Test Event',
|
|
@@ -131,16 +112,24 @@ describe('usePublicEvent', () => {
|
|
|
131
112
|
event_logo: null
|
|
132
113
|
};
|
|
133
114
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
115
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
116
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
117
|
+
...mockEventData,
|
|
118
|
+
event_code: 'test-event',
|
|
119
|
+
is_visible: true,
|
|
120
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
121
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
137
122
|
});
|
|
123
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
124
|
+
mockSupabaseClient.from
|
|
125
|
+
.mockReturnValueOnce(mockEventQueryBuilder)
|
|
126
|
+
.mockReturnValueOnce(mockLogoQueryBuilder);
|
|
138
127
|
|
|
139
128
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
140
129
|
|
|
141
130
|
await waitFor(() => {
|
|
142
131
|
expect(result.current.isLoading).toBe(false);
|
|
143
|
-
}, { interval: 10 });
|
|
132
|
+
}, { interval: 10, timeout: 2000 });
|
|
144
133
|
|
|
145
134
|
expect(result.current.event).toEqual({
|
|
146
135
|
id: '123',
|
|
@@ -210,22 +199,30 @@ describe('usePublicEvent', () => {
|
|
|
210
199
|
event_email: 'event@example.com'
|
|
211
200
|
};
|
|
212
201
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
202
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
203
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
204
|
+
...mockEventData,
|
|
205
|
+
event_code: 'test-event',
|
|
206
|
+
is_visible: true,
|
|
207
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
208
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
216
209
|
});
|
|
210
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
211
|
+
mockSupabaseClient.from
|
|
212
|
+
.mockReturnValueOnce(mockEventQueryBuilder)
|
|
213
|
+
.mockReturnValueOnce(mockLogoQueryBuilder);
|
|
217
214
|
|
|
218
215
|
const { result, rerender } = renderHook(() => usePublicEvent('test-event'));
|
|
219
216
|
|
|
220
217
|
await waitFor(() => {
|
|
221
218
|
expect(result.current.isLoading).toBe(false);
|
|
222
|
-
}, { interval: 10 });
|
|
219
|
+
}, { interval: 10, timeout: 2000 });
|
|
223
220
|
|
|
224
|
-
// Rerender with same event code - should use cache
|
|
221
|
+
// Rerender with same event code - should use cache (no new queries)
|
|
225
222
|
rerender();
|
|
226
223
|
|
|
227
|
-
// Should not call
|
|
228
|
-
expect(mockSupabaseClient.
|
|
224
|
+
// Should not call query again (cached) - only initial fetch: 2 calls (event + logo)
|
|
225
|
+
expect(mockSupabaseClient.from).toHaveBeenCalledTimes(2);
|
|
229
226
|
});
|
|
230
227
|
|
|
231
228
|
it('should respect cache TTL', async () => {
|
|
@@ -248,28 +245,40 @@ describe('usePublicEvent', () => {
|
|
|
248
245
|
event_email: 'event@example.com'
|
|
249
246
|
};
|
|
250
247
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
248
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
249
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
250
|
+
...mockEventData,
|
|
251
|
+
event_code: 'test-event',
|
|
252
|
+
is_visible: true,
|
|
253
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
254
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
254
255
|
});
|
|
256
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
257
|
+
// Need mocks for both hook instances: initial (2 calls) + after cache expires (2 calls) = 4 total
|
|
258
|
+
mockSupabaseClient.from
|
|
259
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // First hook: event
|
|
260
|
+
.mockReturnValueOnce(mockLogoQueryBuilder) // First hook: logo
|
|
261
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // Second hook (after cache expires): event
|
|
262
|
+
.mockReturnValueOnce(mockLogoQueryBuilder); // Second hook (after cache expires): logo
|
|
255
263
|
|
|
256
264
|
const { result } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
|
|
257
265
|
|
|
258
266
|
await waitFor(() => {
|
|
259
267
|
expect(result.current.isLoading).toBe(false);
|
|
260
|
-
}, { interval: 10 });
|
|
268
|
+
}, { interval: 10, timeout: 2000 });
|
|
261
269
|
|
|
262
270
|
// Wait for cache to expire
|
|
263
271
|
await new Promise(resolve => setTimeout(resolve, 150));
|
|
264
272
|
|
|
265
|
-
//
|
|
273
|
+
// New hook instance should fetch again (cache expired)
|
|
266
274
|
const { result: result2 } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
|
|
267
275
|
|
|
268
276
|
await waitFor(() => {
|
|
269
277
|
expect(result2.current.isLoading).toBe(false);
|
|
270
|
-
}, { interval: 10 });
|
|
278
|
+
}, { interval: 10, timeout: 2000 });
|
|
271
279
|
|
|
272
|
-
|
|
280
|
+
// Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
|
|
281
|
+
expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
|
|
273
282
|
});
|
|
274
283
|
|
|
275
284
|
it('should disable caching when requested', async () => {
|
|
@@ -297,14 +306,33 @@ describe('usePublicEvent', () => {
|
|
|
297
306
|
const { clearPublicEventCache } = await import('../public/usePublicEvent');
|
|
298
307
|
clearPublicEventCache();
|
|
299
308
|
|
|
300
|
-
//
|
|
309
|
+
// usePublicEvent makes TWO queries per fetch: core_events + core_file_references (logo)
|
|
301
310
|
let callCount = 0;
|
|
302
|
-
|
|
311
|
+
const mockEventQueryBuilder1 = createMockQueryBuilder({
|
|
312
|
+
...mockEventData,
|
|
313
|
+
event_code: 'test-event',
|
|
314
|
+
is_visible: true,
|
|
315
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
316
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
317
|
+
});
|
|
318
|
+
const mockLogoQueryBuilder1 = createMockQueryBuilder(null, null);
|
|
319
|
+
const mockEventQueryBuilder2 = createMockQueryBuilder({
|
|
320
|
+
...mockEventData,
|
|
321
|
+
event_code: 'test-event-2',
|
|
322
|
+
is_visible: true,
|
|
323
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
324
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
325
|
+
});
|
|
326
|
+
const mockLogoQueryBuilder2 = createMockQueryBuilder(null, null);
|
|
327
|
+
|
|
328
|
+
mockSupabaseClient.from.mockImplementation(() => {
|
|
303
329
|
callCount++;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
330
|
+
// First fetch: event1 + logo1, Second fetch: event2 + logo2
|
|
331
|
+
if (callCount <= 2) {
|
|
332
|
+
return callCount === 1 ? mockEventQueryBuilder1 : mockLogoQueryBuilder1;
|
|
333
|
+
} else {
|
|
334
|
+
return callCount === 3 ? mockEventQueryBuilder2 : mockLogoQueryBuilder2;
|
|
335
|
+
}
|
|
308
336
|
});
|
|
309
337
|
|
|
310
338
|
const { result, rerender } = renderHook(
|
|
@@ -314,44 +342,36 @@ describe('usePublicEvent', () => {
|
|
|
314
342
|
|
|
315
343
|
await waitFor(() => {
|
|
316
344
|
expect(result.current.isLoading).toBe(false);
|
|
317
|
-
}, { interval: 10 });
|
|
345
|
+
}, { interval: 10, timeout: 2000 });
|
|
318
346
|
|
|
319
347
|
// Change the event code to force a new fetch
|
|
320
348
|
rerender({ eventCode: 'test-event-2' });
|
|
321
349
|
|
|
322
350
|
await waitFor(() => {
|
|
323
351
|
expect(result.current.isLoading).toBe(false);
|
|
324
|
-
}, { interval: 10 });
|
|
352
|
+
}, { interval: 10, timeout: 2000 });
|
|
325
353
|
|
|
326
|
-
|
|
354
|
+
// Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
|
|
355
|
+
expect(callCount).toBe(4);
|
|
327
356
|
});
|
|
328
357
|
});
|
|
329
358
|
|
|
330
359
|
describe('Error Handling', () => {
|
|
331
|
-
it('should handle
|
|
332
|
-
//
|
|
360
|
+
it('should handle query errors', async () => {
|
|
361
|
+
// usePublicEvent uses table query, not RPC
|
|
333
362
|
const testError = { message: 'Database error', details: 'Test error details', hint: null, code: 'TEST_ERROR' };
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
data: null,
|
|
337
|
-
error: testError
|
|
338
|
-
})
|
|
339
|
-
);
|
|
363
|
+
const mockQueryBuilder = createMockQueryBuilder(null, testError);
|
|
364
|
+
mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
|
|
340
365
|
|
|
341
366
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
342
367
|
|
|
343
368
|
await waitFor(() => {
|
|
344
369
|
expect(result.current.isLoading).toBe(false);
|
|
345
|
-
}, { interval: 10 });
|
|
370
|
+
}, { interval: 10, timeout: 2000 });
|
|
346
371
|
|
|
347
372
|
expect(result.current.error).toBeInstanceOf(Error);
|
|
348
373
|
expect(result.current.error?.message).toBe('Database error');
|
|
349
374
|
expect(result.current.event).toBe(null);
|
|
350
|
-
|
|
351
|
-
// Verify the mock was called
|
|
352
|
-
expect(mockSupabaseClient.rpc).toHaveBeenCalledWith('get_public_event_by_code', {
|
|
353
|
-
event_code_param: 'test-event'
|
|
354
|
-
});
|
|
355
375
|
});
|
|
356
376
|
|
|
357
377
|
it('should handle missing Supabase client', async () => {
|
|
@@ -411,16 +431,24 @@ describe('usePublicEvent', () => {
|
|
|
411
431
|
event_logo: null
|
|
412
432
|
};
|
|
413
433
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
434
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
435
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
436
|
+
...mockEventData,
|
|
437
|
+
event_code: 'test-event',
|
|
438
|
+
is_visible: true,
|
|
439
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
440
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
417
441
|
});
|
|
442
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
443
|
+
mockSupabaseClient.from
|
|
444
|
+
.mockReturnValueOnce(mockEventQueryBuilder)
|
|
445
|
+
.mockReturnValueOnce(mockLogoQueryBuilder);
|
|
418
446
|
|
|
419
447
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
420
448
|
|
|
421
449
|
await waitFor(() => {
|
|
422
450
|
expect(result.current.isLoading).toBe(false);
|
|
423
|
-
}, { interval: 10 });
|
|
451
|
+
}, { interval: 10, timeout: 2000 });
|
|
424
452
|
|
|
425
453
|
expect(result.current.event).toBeTruthy();
|
|
426
454
|
});
|
|
@@ -447,23 +475,35 @@ describe('usePublicEvent', () => {
|
|
|
447
475
|
event_email: 'event@example.com'
|
|
448
476
|
};
|
|
449
477
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
478
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
479
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
480
|
+
...mockEventData,
|
|
481
|
+
event_code: 'test-event',
|
|
482
|
+
is_visible: true,
|
|
483
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
484
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
453
485
|
});
|
|
486
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
487
|
+
// Need mocks for initial fetch (2 calls) + refetch (2 calls) = 4 total
|
|
488
|
+
mockSupabaseClient.from
|
|
489
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // Initial: event
|
|
490
|
+
.mockReturnValueOnce(mockLogoQueryBuilder) // Initial: logo
|
|
491
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // Refetch: event
|
|
492
|
+
.mockReturnValueOnce(mockLogoQueryBuilder); // Refetch: logo
|
|
454
493
|
|
|
455
494
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
456
495
|
|
|
457
496
|
await waitFor(() => {
|
|
458
497
|
expect(result.current.isLoading).toBe(false);
|
|
459
|
-
}, { interval: 10 });
|
|
498
|
+
}, { interval: 10, timeout: 2000 });
|
|
460
499
|
|
|
461
500
|
// Call refetch
|
|
462
501
|
await act(async () => {
|
|
463
502
|
await result.current.refetch();
|
|
464
503
|
});
|
|
465
504
|
|
|
466
|
-
|
|
505
|
+
// Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
|
|
506
|
+
expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
|
|
467
507
|
});
|
|
468
508
|
|
|
469
509
|
it('should clear cache when refetch is called', async () => {
|
|
@@ -486,24 +526,36 @@ describe('usePublicEvent', () => {
|
|
|
486
526
|
event_email: 'event@example.com'
|
|
487
527
|
};
|
|
488
528
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
529
|
+
// usePublicEvent makes TWO queries: core_events + core_file_references (logo)
|
|
530
|
+
const mockEventQueryBuilder = createMockQueryBuilder({
|
|
531
|
+
...mockEventData,
|
|
532
|
+
event_code: 'test-event',
|
|
533
|
+
is_visible: true,
|
|
534
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
535
|
+
updated_at: '2024-01-01T00:00:00Z'
|
|
492
536
|
});
|
|
537
|
+
const mockLogoQueryBuilder = createMockQueryBuilder(null, null);
|
|
538
|
+
// Need mocks for initial fetch (2 calls) + refetch (2 calls) = 4 total
|
|
539
|
+
mockSupabaseClient.from
|
|
540
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // Initial: event
|
|
541
|
+
.mockReturnValueOnce(mockLogoQueryBuilder) // Initial: logo
|
|
542
|
+
.mockReturnValueOnce(mockEventQueryBuilder) // Refetch: event
|
|
543
|
+
.mockReturnValueOnce(mockLogoQueryBuilder); // Refetch: logo
|
|
493
544
|
|
|
494
545
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
495
546
|
|
|
496
547
|
await waitFor(() => {
|
|
497
548
|
expect(result.current.isLoading).toBe(false);
|
|
498
|
-
}, { interval: 10 });
|
|
549
|
+
}, { interval: 10, timeout: 2000 });
|
|
499
550
|
|
|
500
551
|
// Call refetch
|
|
501
552
|
await act(async () => {
|
|
502
553
|
await result.current.refetch();
|
|
503
554
|
});
|
|
504
555
|
|
|
505
|
-
// Should call
|
|
506
|
-
|
|
556
|
+
// Should call query again (cache was cleared)
|
|
557
|
+
// Each fetch makes 2 calls (event + logo), so 2 fetches = 4 calls
|
|
558
|
+
expect(mockSupabaseClient.from).toHaveBeenCalledTimes(4);
|
|
507
559
|
});
|
|
508
560
|
});
|
|
509
561
|
|
|
@@ -530,52 +582,49 @@ describe('usePublicEvent', () => {
|
|
|
530
582
|
});
|
|
531
583
|
|
|
532
584
|
describe('Edge Cases', () => {
|
|
533
|
-
it('should handle null event data from
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
});
|
|
585
|
+
it('should handle null event data from query', async () => {
|
|
586
|
+
// usePublicEvent uses table query, not RPC
|
|
587
|
+
const mockQueryBuilder = createMockQueryBuilder(null, null);
|
|
588
|
+
mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
|
|
538
589
|
|
|
539
590
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
540
591
|
|
|
541
592
|
await waitFor(() => {
|
|
542
593
|
expect(result.current.isLoading).toBe(false);
|
|
543
|
-
}, { interval: 10 });
|
|
594
|
+
}, { interval: 10, timeout: 2000 });
|
|
544
595
|
|
|
545
596
|
expect(result.current.event).toBe(null);
|
|
546
|
-
expect(result.current.error).
|
|
597
|
+
expect(result.current.error?.message).toBe('Event not found');
|
|
547
598
|
});
|
|
548
599
|
|
|
549
|
-
it('should handle
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
});
|
|
600
|
+
it('should handle query returning null (event not found)', async () => {
|
|
601
|
+
// usePublicEvent uses table query, not RPC
|
|
602
|
+
const mockQueryBuilder = createMockQueryBuilder(null, { code: 'PGRST116', message: 'No rows found' });
|
|
603
|
+
mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
|
|
554
604
|
|
|
555
605
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
556
606
|
|
|
557
607
|
await waitFor(() => {
|
|
558
608
|
expect(result.current.isLoading).toBe(false);
|
|
559
|
-
}, { interval: 10 });
|
|
609
|
+
}, { interval: 10, timeout: 2000 });
|
|
560
610
|
|
|
561
611
|
expect(result.current.event).toBe(null);
|
|
562
|
-
expect(result.current.error).
|
|
612
|
+
expect(result.current.error?.message).toBe('Event not found');
|
|
563
613
|
});
|
|
564
614
|
|
|
565
|
-
it('should handle undefined event data from
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
});
|
|
615
|
+
it('should handle undefined event data from query', async () => {
|
|
616
|
+
// usePublicEvent uses table query, not RPC
|
|
617
|
+
const mockQueryBuilder = createMockQueryBuilder(null, null);
|
|
618
|
+
mockSupabaseClient.from.mockReturnValue(mockQueryBuilder);
|
|
570
619
|
|
|
571
620
|
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
572
621
|
|
|
573
622
|
await waitFor(() => {
|
|
574
623
|
expect(result.current.isLoading).toBe(false);
|
|
575
|
-
}, { interval: 10 });
|
|
624
|
+
}, { interval: 10, timeout: 2000 });
|
|
576
625
|
|
|
577
626
|
expect(result.current.event).toBe(null);
|
|
578
|
-
expect(result.current.error).
|
|
627
|
+
expect(result.current.error?.message).toBe('Event not found');
|
|
579
628
|
});
|
|
580
629
|
});
|
|
581
630
|
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useSessionDraft Hook Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/__tests__
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
8
|
+
import { renderHook, act } from '@testing-library/react';
|
|
9
|
+
import { useSessionDraft } from '../useSessionDraft';
|
|
10
|
+
|
|
11
|
+
describe('useSessionDraft', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Clear sessionStorage before each test
|
|
14
|
+
sessionStorage.clear();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
// Clear sessionStorage after each test
|
|
19
|
+
sessionStorage.clear();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should initialize with initialState when no draft exists', () => {
|
|
23
|
+
const { result } = renderHook(() =>
|
|
24
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(result.current.state).toEqual({ value: 'initial' });
|
|
28
|
+
expect(result.current.wasRestored).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should restore state from sessionStorage on mount', () => {
|
|
32
|
+
// Set up initial draft
|
|
33
|
+
const draft = {
|
|
34
|
+
version: 1,
|
|
35
|
+
data: { value: 'restored' },
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
};
|
|
38
|
+
sessionStorage.setItem('pace-core:draft:test-key', JSON.stringify(draft));
|
|
39
|
+
|
|
40
|
+
const { result } = renderHook(() =>
|
|
41
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result.current.state).toEqual({ value: 'restored' });
|
|
45
|
+
expect(result.current.wasRestored).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should persist state changes with debouncing', async () => {
|
|
49
|
+
vi.useFakeTimers();
|
|
50
|
+
|
|
51
|
+
const { result } = renderHook(() =>
|
|
52
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.setState({ value: 'updated' });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// State should update immediately
|
|
60
|
+
expect(result.current.state).toEqual({ value: 'updated' });
|
|
61
|
+
|
|
62
|
+
// But storage should not be updated yet (debounced)
|
|
63
|
+
expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
|
|
64
|
+
|
|
65
|
+
// Fast-forward past debounce delay
|
|
66
|
+
await act(async () => {
|
|
67
|
+
vi.advanceTimersByTime(300);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Now storage should be updated
|
|
71
|
+
const stored = sessionStorage.getItem('pace-core:draft:test-key');
|
|
72
|
+
expect(stored).not.toBeNull();
|
|
73
|
+
if (stored) {
|
|
74
|
+
const parsed = JSON.parse(stored);
|
|
75
|
+
expect(parsed.data).toEqual({ value: 'updated' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
vi.useRealTimers();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should clear draft when clearDraft is called', () => {
|
|
82
|
+
const { result } = renderHook(() =>
|
|
83
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
act(() => {
|
|
87
|
+
result.current.setState({ value: 'updated' });
|
|
88
|
+
result.current.saveImmediately();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(sessionStorage.getItem('pace-core:draft:test-key')).not.toBeNull();
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.clearDraft();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
|
|
98
|
+
expect(result.current.state).toEqual({ value: 'initial' });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle storage quota errors gracefully', () => {
|
|
102
|
+
// Mock sessionStorage.setItem to throw quota error
|
|
103
|
+
const originalSetItem = sessionStorage.setItem;
|
|
104
|
+
sessionStorage.setItem = vi.fn(() => {
|
|
105
|
+
const error = new DOMException('QuotaExceededError');
|
|
106
|
+
error.name = 'QuotaExceededError';
|
|
107
|
+
throw error;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const { result } = renderHook(() =>
|
|
111
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Should not throw, just fail silently
|
|
115
|
+
expect(() => {
|
|
116
|
+
act(() => {
|
|
117
|
+
result.current.setState({ value: 'updated' });
|
|
118
|
+
result.current.saveImmediately();
|
|
119
|
+
});
|
|
120
|
+
}).not.toThrow();
|
|
121
|
+
|
|
122
|
+
// Restore original
|
|
123
|
+
sessionStorage.setItem = originalSetItem;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle corrupted stored data gracefully', () => {
|
|
127
|
+
// Set corrupted data
|
|
128
|
+
sessionStorage.setItem('pace-core:draft:test-key', 'invalid json');
|
|
129
|
+
|
|
130
|
+
const { result } = renderHook(() =>
|
|
131
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Should fall back to initial state
|
|
135
|
+
expect(result.current.state).toEqual({ value: 'initial' });
|
|
136
|
+
expect(result.current.wasRestored).toBe(false);
|
|
137
|
+
|
|
138
|
+
// Corrupted data should be cleared
|
|
139
|
+
expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should reject drafts with mismatched schema version', () => {
|
|
143
|
+
// Set draft with wrong version
|
|
144
|
+
const draft = {
|
|
145
|
+
version: 999, // Wrong version
|
|
146
|
+
data: { value: 'old' },
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
};
|
|
149
|
+
sessionStorage.setItem('pace-core:draft:test-key', JSON.stringify(draft));
|
|
150
|
+
|
|
151
|
+
const { result } = renderHook(() =>
|
|
152
|
+
useSessionDraft('test-key', { value: 'initial' })
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Should fall back to initial state
|
|
156
|
+
expect(result.current.state).toEqual({ value: 'initial' });
|
|
157
|
+
expect(result.current.wasRestored).toBe(false);
|
|
158
|
+
|
|
159
|
+
// Old draft should be cleared
|
|
160
|
+
expect(sessionStorage.getItem('pace-core:draft:test-key')).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
@@ -28,7 +28,8 @@ vi.mock('../../utils/core/logger', () => {
|
|
|
28
28
|
import { createLogger } from '../../utils/core/logger';
|
|
29
29
|
const getMockLogger = () => createLogger('test');
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// Match the actual timeout value from useSessionRestoration.ts
|
|
32
|
+
const SESSION_RESTORATION_TIMEOUT_MS = 10000; // 10 seconds (matches implementation)
|
|
32
33
|
|
|
33
34
|
describe('useSessionRestoration', () => {
|
|
34
35
|
let mockContext: { sessionRestoration: SessionRestorationState };
|
|
@@ -136,9 +137,11 @@ describe('useSessionRestoration', () => {
|
|
|
136
137
|
// Advance time past timeout
|
|
137
138
|
await act(async () => {
|
|
138
139
|
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
|
|
140
|
+
// Wait for state update after timeout
|
|
141
|
+
await vi.runAllTimersAsync();
|
|
139
142
|
});
|
|
140
143
|
|
|
141
|
-
// With fake timers, the timeout should fire
|
|
144
|
+
// With fake timers, the timeout should fire after advancing
|
|
142
145
|
expect(result.current.hasTimedOut).toBe(true);
|
|
143
146
|
|
|
144
147
|
const logger = getMockLogger();
|
|
@@ -310,7 +313,7 @@ describe('useSessionRestoration', () => {
|
|
|
310
313
|
expect(result.current.isRestoring).toBe(true);
|
|
311
314
|
});
|
|
312
315
|
|
|
313
|
-
it('resets hasTimedOut when restoration state changes', () => {
|
|
316
|
+
it('resets hasTimedOut when restoration state changes', async () => {
|
|
314
317
|
mockContext.sessionRestoration = {
|
|
315
318
|
isRestoring: true,
|
|
316
319
|
restorationComplete: false,
|
|
@@ -321,11 +324,13 @@ describe('useSessionRestoration', () => {
|
|
|
321
324
|
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
322
325
|
|
|
323
326
|
// Trigger timeout - advance timers
|
|
324
|
-
act(() => {
|
|
327
|
+
await act(async () => {
|
|
325
328
|
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
|
|
329
|
+
// Wait for state update after timeout
|
|
330
|
+
await vi.runAllTimersAsync();
|
|
326
331
|
});
|
|
327
332
|
|
|
328
|
-
// Verify timeout occurred (with fake timers, setTimeout fires
|
|
333
|
+
// Verify timeout occurred (with fake timers, setTimeout fires after advancing)
|
|
329
334
|
expect(result.current.hasTimedOut).toBe(true);
|
|
330
335
|
|
|
331
336
|
// Reset restoration state
|