@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
|
@@ -1,1034 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
lastUpdated: 2025-11-18T17:00:00+11:00
|
|
3
|
-
version: 0.5.181
|
|
4
|
-
reviewedBy: documentation-standards-audit
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Testing Best Practices
|
|
8
|
-
|
|
9
|
-
Comprehensive testing is essential for maintaining code quality and preventing regressions. This guide provides testing strategies and patterns for `@jmruthers/pace-core` applications.
|
|
10
|
-
|
|
11
|
-
> 📖 **For detailed testing standards and implementation patterns**, see the [Testing Standard](../standards/testing-standard.md) - the authoritative guide for writing world-class tests in pace-core.
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
Testing in `@jmruthers/pace-core` covers multiple layers:
|
|
16
|
-
|
|
17
|
-
- **Unit Testing**: Individual components and functions
|
|
18
|
-
- **Integration Testing**: Component interactions and hooks
|
|
19
|
-
- **E2E Testing**: Complete user workflows
|
|
20
|
-
- **Accessibility Testing**: Screen reader and keyboard navigation
|
|
21
|
-
- **Performance Testing**: Component rendering and memory usage
|
|
22
|
-
- **Security Testing**: Authentication and authorization
|
|
23
|
-
|
|
24
|
-
## Testing Strategy
|
|
25
|
-
|
|
26
|
-
### 1. Testing Pyramid
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// Base: Unit Tests (70%)
|
|
30
|
-
// - Individual components
|
|
31
|
-
// - Utility functions
|
|
32
|
-
// - Hooks
|
|
33
|
-
// - Form validation
|
|
34
|
-
|
|
35
|
-
// Middle: Integration Tests (20%)
|
|
36
|
-
// - Component interactions
|
|
37
|
-
// - Hook combinations
|
|
38
|
-
// - API integration
|
|
39
|
-
|
|
40
|
-
// Top: E2E Tests (10%)
|
|
41
|
-
// - Complete user workflows
|
|
42
|
-
// - Critical user journeys
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 2. Test Organisation
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// File structure
|
|
49
|
-
src/
|
|
50
|
-
├── components/
|
|
51
|
-
│ ├── Button/
|
|
52
|
-
│ │ ├── Button.tsx
|
|
53
|
-
│ │ ├── Button.test.tsx
|
|
54
|
-
│ │ └── __snapshots__/
|
|
55
|
-
│ └── DataTable/
|
|
56
|
-
│ ├── DataTable.tsx
|
|
57
|
-
│ ├── DataTable.test.tsx
|
|
58
|
-
│ └── __snapshots__/
|
|
59
|
-
├── hooks/
|
|
60
|
-
│ ├── useEvents.ts
|
|
61
|
-
│ ├── useEvents.test.ts
|
|
62
|
-
│ └── __mocks__/
|
|
63
|
-
└── utils/
|
|
64
|
-
├── validation.ts
|
|
65
|
-
├── validation.test.ts
|
|
66
|
-
└── __mocks__/
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Unit Testing
|
|
70
|
-
|
|
71
|
-
### 1. Component Testing
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
75
|
-
import { Button } from '@jmruthers/pace-core';
|
|
76
|
-
|
|
77
|
-
describe('Button', () => {
|
|
78
|
-
test('renders with correct text', () => {
|
|
79
|
-
render(<Button>Click me</Button>);
|
|
80
|
-
expect(screen.getByText('Click me')).toBeInTheDocument();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('calls onClick when clicked', () => {
|
|
84
|
-
const handleClick = jest.fn();
|
|
85
|
-
render(<Button onClick={handleClick}>Click me</Button>);
|
|
86
|
-
|
|
87
|
-
fireEvent.click(screen.getByText('Click me'));
|
|
88
|
-
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('is disabled when disabled prop is true', () => {
|
|
92
|
-
render(<Button disabled>Click me</Button>);
|
|
93
|
-
expect(screen.getByText('Click me')).toBeDisabled();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('shows loading state', () => {
|
|
97
|
-
render(<Button loading>Click me</Button>);
|
|
98
|
-
expect(screen.getByText('Click me')).toHaveClass('loading');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('applies variant classes correctly', () => {
|
|
102
|
-
const { rerender } = render(<Button variant="primary">Button</Button>);
|
|
103
|
-
expect(screen.getByText('Button')).toHaveClass('btn-primary');
|
|
104
|
-
|
|
105
|
-
rerender(<Button variant="secondary">Button</Button>);
|
|
106
|
-
expect(screen.getByText('Button')).toHaveClass('btn-secondary');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 2. Hook Testing
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
import { renderHook, act } from '@testing-library/react';
|
|
115
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
116
|
-
|
|
117
|
-
// Mock the Supabase client
|
|
118
|
-
jest.mock('@jmruthers/pace-core', () => ({
|
|
119
|
-
...jest.requireActual('@jmruthers/pace-core'),
|
|
120
|
-
useSupabase: () => ({
|
|
121
|
-
supabase: {
|
|
122
|
-
from: jest.fn().mockReturnThis(),
|
|
123
|
-
select: jest.fn().mockReturnThis(),
|
|
124
|
-
eq: jest.fn().mockReturnThis(),
|
|
125
|
-
},
|
|
126
|
-
}),
|
|
127
|
-
}));
|
|
128
|
-
|
|
129
|
-
describe('useEvents', () => {
|
|
130
|
-
test('returns initial state', () => {
|
|
131
|
-
const { result } = renderHook(() => useEvents());
|
|
132
|
-
|
|
133
|
-
expect(result.current.events).toEqual([]);
|
|
134
|
-
expect(result.current.loading).toBe(true);
|
|
135
|
-
expect(result.current.error).toBeNull();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('fetches events successfully', async () => {
|
|
139
|
-
const mockEvents = [
|
|
140
|
-
{ id: '1', name: 'Event 1', start_date: '2024-01-01' },
|
|
141
|
-
{ id: '2', name: 'Event 2', start_date: '2024-01-02' },
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
// Mock successful API response
|
|
145
|
-
const mockSupabase = {
|
|
146
|
-
from: jest.fn().mockReturnThis(),
|
|
147
|
-
select: jest.fn().mockReturnThis(),
|
|
148
|
-
eq: jest.fn().mockResolvedValue({ data: mockEvents, error: null }),
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const { result } = renderHook(() => useEvents());
|
|
152
|
-
|
|
153
|
-
await act(async () => {
|
|
154
|
-
// Wait for the hook to complete its async operation
|
|
155
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(result.current.events).toEqual(mockEvents);
|
|
159
|
-
expect(result.current.loading).toBe(false);
|
|
160
|
-
expect(result.current.error).toBeNull();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('handles API errors', async () => {
|
|
164
|
-
const mockError = new Error('Failed to fetch events');
|
|
165
|
-
|
|
166
|
-
// Mock failed API response
|
|
167
|
-
const mockSupabase = {
|
|
168
|
-
from: jest.fn().mockReturnThis(),
|
|
169
|
-
select: jest.fn().mockReturnThis(),
|
|
170
|
-
eq: jest.fn().mockResolvedValue({ data: null, error: mockError }),
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const { result } = renderHook(() => useEvents());
|
|
174
|
-
|
|
175
|
-
await act(async () => {
|
|
176
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(result.current.events).toEqual([]);
|
|
180
|
-
expect(result.current.loading).toBe(false);
|
|
181
|
-
expect(result.current.error).toBe(mockError.message);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### 3. Utility Function Testing
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
import { validateEmail, formatDate, calculateEventDuration } from '@jmruthers/pace-core';
|
|
190
|
-
|
|
191
|
-
describe('validation utilities', () => {
|
|
192
|
-
describe('validateEmail', () => {
|
|
193
|
-
test('returns true for valid emails', () => {
|
|
194
|
-
expect(validateEmail('test@example.com')).toBe(true);
|
|
195
|
-
expect(validateEmail('user.name@domain.co.uk')).toBe(true);
|
|
196
|
-
expect(validateEmail('user+tag@example.com')).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('returns false for invalid emails', () => {
|
|
200
|
-
expect(validateEmail('invalid-email')).toBe(false);
|
|
201
|
-
expect(validateEmail('test@')).toBe(false);
|
|
202
|
-
expect(validateEmail('@example.com')).toBe(false);
|
|
203
|
-
expect(validateEmail('')).toBe(false);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('formatDate', () => {
|
|
208
|
-
test('formats date correctly', () => {
|
|
209
|
-
const date = new Date('2024-01-15T10:30:00Z');
|
|
210
|
-
expect(formatDate(date)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/); // "15 Jan 2024" format
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('handles different input types', () => {
|
|
214
|
-
const dateString = '2024-01-15T10:30:00Z';
|
|
215
|
-
const timestamp = new Date(dateString).getTime();
|
|
216
|
-
|
|
217
|
-
expect(formatDate(dateString)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
218
|
-
expect(formatDate(timestamp)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe('formatTime', () => {
|
|
223
|
-
test('formats time correctly in 24-hour format', () => {
|
|
224
|
-
const date = new Date('2024-01-15T14:30:00Z');
|
|
225
|
-
expect(formatTime(date)).toMatch(/^\d{2}:\d{2}$/); // "14:30" format
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
describe('formatDateTime', () => {
|
|
230
|
-
test('formats date and time correctly', () => {
|
|
231
|
-
const date = new Date('2024-01-15T14:30:00Z');
|
|
232
|
-
expect(formatDateTime(date)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}, \d{2}:\d{2}$/); // "15 Jan 2024, 14:30" format
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('calculateEventDuration', () => {
|
|
237
|
-
test('calculates duration in hours', () => {
|
|
238
|
-
const startDate = '2024-01-15T09:00:00Z';
|
|
239
|
-
const endDate = '2024-01-15T17:00:00Z';
|
|
240
|
-
expect(calculateEventDuration(startDate, endDate, 'hours')).toBe(8);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('calculates duration in days', () => {
|
|
244
|
-
const startDate = '2024-01-15T09:00:00Z';
|
|
245
|
-
const endDate = '2024-01-17T09:00:00Z';
|
|
246
|
-
expect(calculateEventDuration(startDate, endDate, 'days')).toBe(2);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
## Integration Testing
|
|
253
|
-
|
|
254
|
-
### 1. Component Integration
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
258
|
-
import { useEvents, useUnifiedAuth } from '@jmruthers/pace-core';
|
|
259
|
-
|
|
260
|
-
// Mock the hooks
|
|
261
|
-
jest.mock('@jmruthers/pace-core', () => ({
|
|
262
|
-
useEvents: jest.fn(),
|
|
263
|
-
useUnifiedAuth: jest.fn(),
|
|
264
|
-
}));
|
|
265
|
-
|
|
266
|
-
describe('EventList Integration', () => {
|
|
267
|
-
beforeEach(() => {
|
|
268
|
-
// Setup default mocks
|
|
269
|
-
(useUnifiedAuth as jest.Mock).mockReturnValue({
|
|
270
|
-
user: { id: '1', email: 'test@example.com' },
|
|
271
|
-
loading: false,
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
(useEvents as jest.Mock).mockReturnValue({
|
|
275
|
-
events: [],
|
|
276
|
-
loading: false,
|
|
277
|
-
error: null,
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test('displays events when user is authenticated', async () => {
|
|
282
|
-
const mockEvents = [
|
|
283
|
-
{ id: '1', name: 'Event 1', start_date: '2024-01-01' },
|
|
284
|
-
{ id: '2', name: 'Event 2', start_date: '2024-01-02' },
|
|
285
|
-
];
|
|
286
|
-
|
|
287
|
-
(useEvents as jest.Mock).mockReturnValue({
|
|
288
|
-
events: mockEvents,
|
|
289
|
-
loading: false,
|
|
290
|
-
error: null,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
render(<EventList />);
|
|
294
|
-
|
|
295
|
-
await waitFor(() => {
|
|
296
|
-
expect(screen.getByText('Event 1')).toBeInTheDocument();
|
|
297
|
-
expect(screen.getByText('Event 2')).toBeInTheDocument();
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test('shows loading state', () => {
|
|
302
|
-
(useEvents as jest.Mock).mockReturnValue({
|
|
303
|
-
events: [],
|
|
304
|
-
loading: true,
|
|
305
|
-
error: null,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
render(<EventList />);
|
|
309
|
-
expect(screen.getByText('Loading events...')).toBeInTheDocument();
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test('shows error state', () => {
|
|
313
|
-
(useEvents as jest.Mock).mockReturnValue({
|
|
314
|
-
events: [],
|
|
315
|
-
loading: false,
|
|
316
|
-
error: 'Failed to load events',
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
render(<EventList />);
|
|
320
|
-
expect(screen.getByText('Error: Failed to load events')).toBeInTheDocument();
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### 2. Form Integration Testing
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
329
|
-
import { useZodForm } from '@jmruthers/pace-core';
|
|
330
|
-
|
|
331
|
-
describe('UserForm Integration', () => {
|
|
332
|
-
test('submits form with valid data', async () => {
|
|
333
|
-
const mockSubmit = jest.fn();
|
|
334
|
-
|
|
335
|
-
render(
|
|
336
|
-
<UserForm onSubmit={mockSubmit} />
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
// Fill out the form
|
|
340
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
341
|
-
target: { value: 'John Doe' },
|
|
342
|
-
});
|
|
343
|
-
fireEvent.change(screen.getByLabelText('Email'), {
|
|
344
|
-
target: { value: 'john@example.com' },
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// Submit the form
|
|
348
|
-
fireEvent.click(screen.getByText('Submit'));
|
|
349
|
-
|
|
350
|
-
await waitFor(() => {
|
|
351
|
-
expect(mockSubmit).toHaveBeenCalledWith({
|
|
352
|
-
name: 'John Doe',
|
|
353
|
-
email: 'john@example.com',
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test('shows validation errors for invalid data', async () => {
|
|
359
|
-
render(<UserForm onSubmit={jest.fn()} />);
|
|
360
|
-
|
|
361
|
-
// Submit without filling required fields
|
|
362
|
-
fireEvent.click(screen.getByText('Submit'));
|
|
363
|
-
|
|
364
|
-
await waitFor(() => {
|
|
365
|
-
expect(screen.getByText('Name is required')).toBeInTheDocument();
|
|
366
|
-
expect(screen.getByText('Invalid email')).toBeInTheDocument();
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test('clears form after successful submission', async () => {
|
|
371
|
-
const mockSubmit = jest.fn().mockResolvedValue(undefined);
|
|
372
|
-
|
|
373
|
-
render(<UserForm onSubmit={mockSubmit} />);
|
|
374
|
-
|
|
375
|
-
// Fill and submit form
|
|
376
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
377
|
-
target: { value: 'John Doe' },
|
|
378
|
-
});
|
|
379
|
-
fireEvent.change(screen.getByLabelText('Email'), {
|
|
380
|
-
target: { value: 'john@example.com' },
|
|
381
|
-
});
|
|
382
|
-
fireEvent.click(screen.getByText('Submit'));
|
|
383
|
-
|
|
384
|
-
await waitFor(() => {
|
|
385
|
-
expect(screen.getByLabelText('Name')).toHaveValue('');
|
|
386
|
-
expect(screen.getByLabelText('Email')).toHaveValue('');
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
## Authentication Testing
|
|
393
|
-
|
|
394
|
-
### 1. Auth Hook Testing
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
import { renderHook, act } from '@testing-library/react';
|
|
398
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
399
|
-
|
|
400
|
-
// Mock Supabase auth
|
|
401
|
-
const mockSupabase = {
|
|
402
|
-
auth: {
|
|
403
|
-
signIn: jest.fn(),
|
|
404
|
-
signUp: jest.fn(),
|
|
405
|
-
signOut: jest.fn(),
|
|
406
|
-
getSession: jest.fn(),
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
jest.mock('@supabase/supabase-js', () => ({
|
|
411
|
-
createClient: () => mockSupabase,
|
|
412
|
-
}));
|
|
413
|
-
|
|
414
|
-
describe('useUnifiedAuth', () => {
|
|
415
|
-
beforeEach(() => {
|
|
416
|
-
jest.clearAllMocks();
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test('signs in successfully', async () => {
|
|
420
|
-
const mockUser = { id: '1', email: 'test@example.com' };
|
|
421
|
-
const mockSession = { user: mockUser, access_token: 'token' };
|
|
422
|
-
|
|
423
|
-
mockSupabase.auth.signIn.mockResolvedValue({
|
|
424
|
-
data: { user: mockUser, session: mockSession },
|
|
425
|
-
error: null,
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const { result } = renderHook(() => useUnifiedAuth());
|
|
429
|
-
|
|
430
|
-
await act(async () => {
|
|
431
|
-
await result.current.signIn({
|
|
432
|
-
email: 'test@example.com',
|
|
433
|
-
password: 'password123',
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
expect(mockSupabase.auth.signIn).toHaveBeenCalledWith({
|
|
438
|
-
email: 'test@example.com',
|
|
439
|
-
password: 'password123',
|
|
440
|
-
});
|
|
441
|
-
expect(result.current.user).toEqual(mockUser);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
test('handles sign in errors', async () => {
|
|
445
|
-
const mockError = { message: 'Invalid credentials' };
|
|
446
|
-
|
|
447
|
-
mockSupabase.auth.signIn.mockResolvedValue({
|
|
448
|
-
data: { user: null, session: null },
|
|
449
|
-
error: mockError,
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
const { result } = renderHook(() => useUnifiedAuth());
|
|
453
|
-
|
|
454
|
-
await act(async () => {
|
|
455
|
-
await result.current.signIn({
|
|
456
|
-
email: 'test@example.com',
|
|
457
|
-
password: 'wrongpassword',
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
expect(result.current.error).toEqual(mockError);
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
test('signs out successfully', async () => {
|
|
465
|
-
mockSupabase.auth.signOut.mockResolvedValue({ error: null });
|
|
466
|
-
|
|
467
|
-
const { result } = renderHook(() => useUnifiedAuth());
|
|
468
|
-
|
|
469
|
-
await act(async () => {
|
|
470
|
-
await result.current.signOut();
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
expect(mockSupabase.auth.signOut).toHaveBeenCalled();
|
|
474
|
-
expect(result.current.user).toBeNull();
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### 2. Protected Route Testing
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
import { render, screen } from '@testing-library/react';
|
|
483
|
-
import { useUnifiedAuth, useRBAC } from '@jmruthers/pace-core';
|
|
484
|
-
|
|
485
|
-
jest.mock('@jmruthers/pace-core', () => ({
|
|
486
|
-
useUnifiedAuth: jest.fn(),
|
|
487
|
-
useRBAC: jest.fn(),
|
|
488
|
-
}));
|
|
489
|
-
|
|
490
|
-
describe('ProtectedRoute', () => {
|
|
491
|
-
test('renders children when user is authenticated and has permission', () => {
|
|
492
|
-
(useUnifiedAuth as jest.Mock).mockReturnValue({
|
|
493
|
-
user: { id: '1', email: 'test@example.com' },
|
|
494
|
-
loading: false,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
(useRBAC as jest.Mock).mockReturnValue({
|
|
498
|
-
hasPermission: jest.fn().mockReturnValue(true),
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
render(
|
|
502
|
-
<ProtectedRoute permission="read:users">
|
|
503
|
-
<div>Protected Content</div>
|
|
504
|
-
</ProtectedRoute>
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
expect(screen.getByText('Protected Content')).toBeInTheDocument();
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test('shows loading when auth is loading', () => {
|
|
511
|
-
(useUnifiedAuth as jest.Mock).mockReturnValue({
|
|
512
|
-
user: null,
|
|
513
|
-
loading: true,
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
render(
|
|
517
|
-
<ProtectedRoute permission="read:users">
|
|
518
|
-
<div>Protected Content</div>
|
|
519
|
-
</ProtectedRoute>
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test('redirects when user is not authenticated', () => {
|
|
526
|
-
(useUnifiedAuth as jest.Mock).mockReturnValue({
|
|
527
|
-
user: null,
|
|
528
|
-
loading: false,
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
render(
|
|
532
|
-
<ProtectedRoute permission="read:users">
|
|
533
|
-
<div>Protected Content</div>
|
|
534
|
-
</ProtectedRoute>
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
expect(screen.getByText('Access denied')).toBeInTheDocument();
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
test('shows access denied when user lacks permission', () => {
|
|
541
|
-
(useUnifiedAuth as jest.Mock).mockReturnValue({
|
|
542
|
-
user: { id: '1', email: 'test@example.com' },
|
|
543
|
-
loading: false,
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
(useRBAC as jest.Mock).mockReturnValue({
|
|
547
|
-
hasPermission: jest.fn().mockReturnValue(false),
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
render(
|
|
551
|
-
<ProtectedRoute permission="read:users">
|
|
552
|
-
<div>Protected Content</div>
|
|
553
|
-
</ProtectedRoute>
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
expect(screen.getByText('Access denied')).toBeInTheDocument();
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## Accessibility Testing
|
|
562
|
-
|
|
563
|
-
### 1. Screen Reader Testing
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
import { render, screen } from '@testing-library/react';
|
|
567
|
-
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
568
|
-
|
|
569
|
-
expect.extend(toHaveNoViolations);
|
|
570
|
-
|
|
571
|
-
describe('Accessibility', () => {
|
|
572
|
-
test('Button has no accessibility violations', async () => {
|
|
573
|
-
const { container } = render(
|
|
574
|
-
<Button aria-label="Submit form">Submit</Button>
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
const results = await axe(container);
|
|
578
|
-
expect(results).toHaveNoViolations();
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
test('DataTable has proper ARIA attributes', () => {
|
|
582
|
-
const columns = [
|
|
583
|
-
{ accessorKey: 'name', header: 'Name' },
|
|
584
|
-
{ accessorKey: 'email', header: 'Email' },
|
|
585
|
-
];
|
|
586
|
-
|
|
587
|
-
const data = [
|
|
588
|
-
{ id: '1', name: 'John Doe', email: 'john@example.com' },
|
|
589
|
-
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' },
|
|
590
|
-
];
|
|
591
|
-
|
|
592
|
-
render(<DataTable data={data} columns={columns} />);
|
|
593
|
-
|
|
594
|
-
// Check for table role
|
|
595
|
-
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
596
|
-
|
|
597
|
-
// Check for table headers
|
|
598
|
-
expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument();
|
|
599
|
-
expect(screen.getByRole('columnheader', { name: 'Email' })).toBeInTheDocument();
|
|
600
|
-
|
|
601
|
-
// Check for table rows
|
|
602
|
-
expect(screen.getAllByRole('row')).toHaveLength(3); // Header + 2 data rows
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
test('Form has proper labels and associations', () => {
|
|
606
|
-
render(
|
|
607
|
-
<form>
|
|
608
|
-
<label htmlFor="name">Name</label>
|
|
609
|
-
<input id="name" type="text" />
|
|
610
|
-
<label htmlFor="email">Email</label>
|
|
611
|
-
<input id="email" type="email" />
|
|
612
|
-
</form>
|
|
613
|
-
);
|
|
614
|
-
|
|
615
|
-
// Check that labels are properly associated
|
|
616
|
-
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
617
|
-
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
618
|
-
});
|
|
619
|
-
});
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
### 2. Keyboard Navigation Testing
|
|
623
|
-
|
|
624
|
-
```typescript
|
|
625
|
-
import { render, screen } from '@testing-library/react';
|
|
626
|
-
import userEvent from '@testing-library/user-event';
|
|
627
|
-
|
|
628
|
-
describe('Keyboard Navigation', () => {
|
|
629
|
-
test('supports tab navigation', async () => {
|
|
630
|
-
const user = userEvent.setup();
|
|
631
|
-
|
|
632
|
-
render(
|
|
633
|
-
<div>
|
|
634
|
-
<button>First Button</button>
|
|
635
|
-
<input type="text" placeholder="Input field" />
|
|
636
|
-
<button>Second Button</button>
|
|
637
|
-
</div>
|
|
638
|
-
);
|
|
639
|
-
|
|
640
|
-
// Focus first element
|
|
641
|
-
await user.tab();
|
|
642
|
-
expect(screen.getByText('First Button')).toHaveFocus();
|
|
643
|
-
|
|
644
|
-
// Tab to next element
|
|
645
|
-
await user.tab();
|
|
646
|
-
expect(screen.getByPlaceholderText('Input field')).toHaveFocus();
|
|
647
|
-
|
|
648
|
-
// Tab to next element
|
|
649
|
-
await user.tab();
|
|
650
|
-
expect(screen.getByText('Second Button')).toHaveFocus();
|
|
651
|
-
|
|
652
|
-
// Tab should wrap around
|
|
653
|
-
await user.tab();
|
|
654
|
-
expect(screen.getByText('First Button')).toHaveFocus();
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
test('supports Enter key activation', async () => {
|
|
658
|
-
const user = userEvent.setup();
|
|
659
|
-
const handleClick = jest.fn();
|
|
660
|
-
|
|
661
|
-
render(<Button onClick={handleClick}>Click me</Button>);
|
|
662
|
-
|
|
663
|
-
await user.tab();
|
|
664
|
-
expect(screen.getByText('Click me')).toHaveFocus();
|
|
665
|
-
|
|
666
|
-
await user.keyboard('{Enter}');
|
|
667
|
-
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
test('supports Escape key for modal closing', async () => {
|
|
671
|
-
const user = userEvent.setup();
|
|
672
|
-
const onClose = jest.fn();
|
|
673
|
-
|
|
674
|
-
render(
|
|
675
|
-
<Modal isOpen={true} onClose={onClose}>
|
|
676
|
-
<div>Modal content</div>
|
|
677
|
-
</Modal>
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
await user.keyboard('{Escape}');
|
|
681
|
-
expect(onClose).toHaveBeenCalledTimes(1);
|
|
682
|
-
});
|
|
683
|
-
});
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
## Performance Testing
|
|
687
|
-
|
|
688
|
-
### 1. Component Performance Testing
|
|
689
|
-
|
|
690
|
-
```typescript
|
|
691
|
-
import { render } from '@testing-library/react';
|
|
692
|
-
import { useComponentPerformance } from '@jmruthers/pace-core';
|
|
693
|
-
|
|
694
|
-
describe('Component Performance', () => {
|
|
695
|
-
test('renders within performance budget', () => {
|
|
696
|
-
const startTime = performance.now();
|
|
697
|
-
|
|
698
|
-
render(<DataTable data={largeDataset} columns={columns} />);
|
|
699
|
-
|
|
700
|
-
const endTime = performance.now();
|
|
701
|
-
const renderTime = endTime - startTime;
|
|
702
|
-
|
|
703
|
-
// Should render within 100ms
|
|
704
|
-
expect(renderTime).toBeLessThan(100);
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
test('does not cause memory leaks', () => {
|
|
708
|
-
const initialMemory = performance.memory?.usedJSHeapSize || 0;
|
|
709
|
-
|
|
710
|
-
// Render and unmount component multiple times
|
|
711
|
-
for (let i = 0; i < 100; i++) {
|
|
712
|
-
const { unmount } = render(<EventList />);
|
|
713
|
-
unmount();
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const finalMemory = performance.memory?.usedJSHeapSize || 0;
|
|
717
|
-
const memoryIncrease = finalMemory - initialMemory;
|
|
718
|
-
|
|
719
|
-
// Memory increase should be minimal (less than 1MB)
|
|
720
|
-
expect(memoryIncrease).toBeLessThan(1024 * 1024);
|
|
721
|
-
});
|
|
722
|
-
});
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### 2. Hook Performance Testing
|
|
726
|
-
|
|
727
|
-
```typescript
|
|
728
|
-
import { renderHook } from '@testing-library/react';
|
|
729
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
730
|
-
|
|
731
|
-
describe('Hook Performance', () => {
|
|
732
|
-
test('useEvents renders efficiently', () => {
|
|
733
|
-
const startTime = performance.now();
|
|
734
|
-
|
|
735
|
-
renderHook(() => useEvents());
|
|
736
|
-
|
|
737
|
-
const endTime = performance.now();
|
|
738
|
-
const renderTime = endTime - startTime;
|
|
739
|
-
|
|
740
|
-
// Hook should initialize within 50ms
|
|
741
|
-
expect(renderTime).toBeLessThan(50);
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
test('useEvents handles large datasets efficiently', () => {
|
|
745
|
-
const largeDataset = Array.from({ length: 1000 }, (_, i) => ({
|
|
746
|
-
id: i.toString(),
|
|
747
|
-
name: `Event ${i}`,
|
|
748
|
-
start_date: '2024-01-01',
|
|
749
|
-
}));
|
|
750
|
-
|
|
751
|
-
const { result } = renderHook(() => useEvents());
|
|
752
|
-
|
|
753
|
-
// Simulate large dataset
|
|
754
|
-
act(() => {
|
|
755
|
-
result.current.setEvents(largeDataset);
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
expect(result.current.events).toHaveLength(1000);
|
|
759
|
-
expect(result.current.loading).toBe(false);
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
## E2E Testing
|
|
765
|
-
|
|
766
|
-
### 1. User Workflow Testing
|
|
767
|
-
|
|
768
|
-
```typescript
|
|
769
|
-
import { test, expect } from '@playwright/test';
|
|
770
|
-
|
|
771
|
-
test('complete user registration and event creation workflow', async ({ page }) => {
|
|
772
|
-
// Navigate to the app
|
|
773
|
-
await page.goto('http://localhost:3000');
|
|
774
|
-
|
|
775
|
-
// Register new user
|
|
776
|
-
await page.click('text=Sign Up');
|
|
777
|
-
await page.fill('[data-testid="email-input"]', 'test@example.com');
|
|
778
|
-
await page.fill('[data-testid="password-input"]', 'password123');
|
|
779
|
-
await page.click('[data-testid="signup-button"]');
|
|
780
|
-
|
|
781
|
-
// Wait for registration to complete
|
|
782
|
-
await page.waitForSelector('[data-testid="dashboard"]');
|
|
783
|
-
|
|
784
|
-
// Create an event
|
|
785
|
-
await page.click('[data-testid="create-event-button"]');
|
|
786
|
-
await page.fill('[data-testid="event-name-input"]', 'Test Event');
|
|
787
|
-
await page.fill('[data-testid="event-description-input"]', 'Test Description');
|
|
788
|
-
await page.fill('[data-testid="event-date-input"]', '2024-12-31');
|
|
789
|
-
await page.click('[data-testid="save-event-button"]');
|
|
790
|
-
|
|
791
|
-
// Verify event was created
|
|
792
|
-
await page.waitForSelector('text=Test Event');
|
|
793
|
-
expect(await page.isVisible('text=Test Event')).toBeTruthy();
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
test('user authentication and permission-based access', async ({ page }) => {
|
|
797
|
-
await page.goto('http://localhost:3000');
|
|
798
|
-
|
|
799
|
-
// Sign in as admin user
|
|
800
|
-
await page.fill('[data-testid="email-input"]', 'admin@example.com');
|
|
801
|
-
await page.fill('[data-testid="password-input"]', 'adminpass');
|
|
802
|
-
await page.click('[data-testid="signin-button"]');
|
|
803
|
-
|
|
804
|
-
await page.waitForSelector('[data-testid="dashboard"]');
|
|
805
|
-
|
|
806
|
-
// Verify admin features are available
|
|
807
|
-
expect(await page.isVisible('[data-testid="admin-panel"]')).toBeTruthy();
|
|
808
|
-
expect(await page.isVisible('[data-testid="user-management"]')).toBeTruthy();
|
|
809
|
-
|
|
810
|
-
// Sign in as regular user
|
|
811
|
-
await page.click('[data-testid="signout-button"]');
|
|
812
|
-
await page.fill('[data-testid="email-input"]', 'user@example.com');
|
|
813
|
-
await page.fill('[data-testid="password-input"]', 'userpass');
|
|
814
|
-
await page.click('[data-testid="signin-button"]');
|
|
815
|
-
|
|
816
|
-
await page.waitForSelector('[data-testid="dashboard"]');
|
|
817
|
-
|
|
818
|
-
// Verify admin features are not available
|
|
819
|
-
expect(await page.isVisible('[data-testid="admin-panel"]')).toBeFalsy();
|
|
820
|
-
expect(await page.isVisible('[data-testid="user-management"]')).toBeFalsy();
|
|
821
|
-
});
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
### 2. Critical Path Testing
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
test('event management critical path', async ({ page }) => {
|
|
828
|
-
await page.goto('http://localhost:3000');
|
|
829
|
-
|
|
830
|
-
// Sign in
|
|
831
|
-
await page.fill('[data-testid="email-input"]', 'test@example.com');
|
|
832
|
-
await page.fill('[data-testid="password-input"]', 'password123');
|
|
833
|
-
await page.click('[data-testid="signin-button"]');
|
|
834
|
-
|
|
835
|
-
await page.waitForSelector('[data-testid="dashboard"]');
|
|
836
|
-
|
|
837
|
-
// Create event
|
|
838
|
-
await page.click('[data-testid="create-event"]');
|
|
839
|
-
await page.fill('[data-testid="event-name"]', 'Critical Test Event');
|
|
840
|
-
await page.fill('[data-testid="event-date"]', '2024-12-31');
|
|
841
|
-
await page.click('[data-testid="save-event"]');
|
|
842
|
-
|
|
843
|
-
// Verify event appears in list
|
|
844
|
-
await page.waitForSelector('text=Critical Test Event');
|
|
845
|
-
|
|
846
|
-
// Edit event
|
|
847
|
-
await page.click('[data-testid="edit-event-Critical Test Event"]');
|
|
848
|
-
await page.fill('[data-testid="event-name"]', 'Updated Critical Test Event');
|
|
849
|
-
await page.click('[data-testid="save-event"]');
|
|
850
|
-
|
|
851
|
-
// Verify event was updated
|
|
852
|
-
await page.waitForSelector('text=Updated Critical Test Event');
|
|
853
|
-
|
|
854
|
-
// Delete event
|
|
855
|
-
await page.click('[data-testid="delete-event-Updated Critical Test Event"]');
|
|
856
|
-
await page.click('[data-testid="confirm-delete"]');
|
|
857
|
-
|
|
858
|
-
// Verify event was deleted
|
|
859
|
-
await page.waitForSelector('text=Updated Critical Test Event', { state: 'hidden' });
|
|
860
|
-
});
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
## Test Utilities
|
|
864
|
-
|
|
865
|
-
### 1. Custom Test Helpers
|
|
866
|
-
|
|
867
|
-
```typescript
|
|
868
|
-
// test-utils.tsx
|
|
869
|
-
import { render, RenderOptions } from '@testing-library/react';
|
|
870
|
-
import { UnifiedAuthProvider, OrganisationProvider } from '@jmruthers/pace-core';
|
|
871
|
-
|
|
872
|
-
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
|
|
873
|
-
return (
|
|
874
|
-
<UnifiedAuthProvider>
|
|
875
|
-
<OrganisationProvider>
|
|
876
|
-
{children}
|
|
877
|
-
</OrganisationProvider>
|
|
878
|
-
</UnifiedAuthProvider>
|
|
879
|
-
);
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
const customRender = (
|
|
883
|
-
ui: React.ReactElement,
|
|
884
|
-
options?: Omit<RenderOptions, 'wrapper'>
|
|
885
|
-
) => render(ui, { wrapper: AllTheProviders, ...options });
|
|
886
|
-
|
|
887
|
-
export * from '@testing-library/react';
|
|
888
|
-
export { customRender as render };
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
### 2. Mock Data Factories
|
|
892
|
-
|
|
893
|
-
```typescript
|
|
894
|
-
// test-factories.ts
|
|
895
|
-
export const createMockUser = (overrides = {}) => ({
|
|
896
|
-
id: '1',
|
|
897
|
-
email: 'test@example.com',
|
|
898
|
-
role: 'user',
|
|
899
|
-
created_at: '2024-01-01T00:00:00Z',
|
|
900
|
-
...overrides,
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
export const createMockEvent = (overrides = {}) => ({
|
|
904
|
-
id: '1',
|
|
905
|
-
name: 'Test Event',
|
|
906
|
-
description: 'Test Description',
|
|
907
|
-
start_date: '2024-12-31T10:00:00Z',
|
|
908
|
-
end_date: '2024-12-31T18:00:00Z',
|
|
909
|
-
organisation_id: '1',
|
|
910
|
-
created_at: '2024-01-01T00:00:00Z',
|
|
911
|
-
...overrides,
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
export const createMockOrganisation = (overrides = {}) => ({
|
|
915
|
-
id: '1',
|
|
916
|
-
name: 'Test Organisation',
|
|
917
|
-
slug: 'test-organisation',
|
|
918
|
-
created_at: '2024-01-01T00:00:00Z',
|
|
919
|
-
...overrides,
|
|
920
|
-
});
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### 3. Test Setup and Teardown
|
|
924
|
-
|
|
925
|
-
```typescript
|
|
926
|
-
// setupTests.ts
|
|
927
|
-
import '@testing-library/jest-dom';
|
|
928
|
-
import { server } from './mocks/server';
|
|
929
|
-
|
|
930
|
-
beforeAll(() => server.listen());
|
|
931
|
-
afterEach(() => server.resetHandlers());
|
|
932
|
-
afterAll(() => server.close());
|
|
933
|
-
|
|
934
|
-
// Mock IntersectionObserver
|
|
935
|
-
global.IntersectionObserver = class IntersectionObserver {
|
|
936
|
-
constructor() {}
|
|
937
|
-
disconnect() {}
|
|
938
|
-
observe() {}
|
|
939
|
-
unobserve() {}
|
|
940
|
-
};
|
|
941
|
-
|
|
942
|
-
// Mock ResizeObserver
|
|
943
|
-
global.ResizeObserver = class ResizeObserver {
|
|
944
|
-
constructor() {}
|
|
945
|
-
disconnect() {}
|
|
946
|
-
observe() {}
|
|
947
|
-
unobserve() {}
|
|
948
|
-
};
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
## Testing Best Practices Checklist
|
|
952
|
-
|
|
953
|
-
### Unit Testing Checklist
|
|
954
|
-
|
|
955
|
-
- [ ] Test component rendering
|
|
956
|
-
- [ ] Test user interactions
|
|
957
|
-
- [ ] Test prop variations
|
|
958
|
-
- [ ] Test error states
|
|
959
|
-
- [ ] Test loading states
|
|
960
|
-
- [ ] Test accessibility features
|
|
961
|
-
- [ ] Test edge cases
|
|
962
|
-
- [ ] Mock external dependencies
|
|
963
|
-
- [ ] Use descriptive test names
|
|
964
|
-
- [ ] Keep tests focused and simple
|
|
965
|
-
|
|
966
|
-
### Integration Testing Checklist
|
|
967
|
-
|
|
968
|
-
- [ ] Test component interactions
|
|
969
|
-
- [ ] Test hook combinations
|
|
970
|
-
- [ ] Test form submissions
|
|
971
|
-
- [ ] Test API integrations
|
|
972
|
-
- [ ] Test authentication flows
|
|
973
|
-
- [ ] Test permission checks
|
|
974
|
-
- [ ] Test error handling
|
|
975
|
-
- [ ] Test loading states
|
|
976
|
-
- [ ] Test data flow
|
|
977
|
-
- [ ] Test user workflows
|
|
978
|
-
|
|
979
|
-
### E2E Testing Checklist
|
|
980
|
-
|
|
981
|
-
- [ ] Test critical user journeys
|
|
982
|
-
- [ ] Test authentication flows
|
|
983
|
-
- [ ] Test permission-based access
|
|
984
|
-
- [ ] Test form submissions
|
|
985
|
-
- [ ] Test data persistence
|
|
986
|
-
- [ ] Test error scenarios
|
|
987
|
-
- [ ] Test responsive design
|
|
988
|
-
- [ ] Test cross-browser compatibility
|
|
989
|
-
- [ ] Test performance under load
|
|
990
|
-
- [ ] Test accessibility compliance
|
|
991
|
-
|
|
992
|
-
### Accessibility Testing Checklist
|
|
993
|
-
|
|
994
|
-
- [ ] Test screen reader compatibility
|
|
995
|
-
- [ ] Test keyboard navigation
|
|
996
|
-
- [ ] Test focus management
|
|
997
|
-
- [ ] Test ARIA attributes
|
|
998
|
-
- [ ] Test color contrast
|
|
999
|
-
- [ ] Test semantic HTML
|
|
1000
|
-
- [ ] Test alternative text
|
|
1001
|
-
- [ ] Test form labels
|
|
1002
|
-
- [ ] Test error announcements
|
|
1003
|
-
- [ ] Test skip links
|
|
1004
|
-
|
|
1005
|
-
## ⚠️ Edge Cases
|
|
1006
|
-
|
|
1007
|
-
### Testing Complex Scenarios
|
|
1008
|
-
|
|
1009
|
-
When testing complex scenarios:
|
|
1010
|
-
- Test with realistic data volumes
|
|
1011
|
-
- Verify edge cases are covered
|
|
1012
|
-
- Test with different user roles and permissions
|
|
1013
|
-
- Ensure tests handle error conditions
|
|
1014
|
-
- Test with concurrent operations
|
|
1015
|
-
|
|
1016
|
-
### Flaky Tests
|
|
1017
|
-
|
|
1018
|
-
When tests are flaky:
|
|
1019
|
-
- Identify root causes of flakiness
|
|
1020
|
-
- Use proper wait strategies
|
|
1021
|
-
- Avoid timing-dependent tests
|
|
1022
|
-
- Mock external dependencies consistently
|
|
1023
|
-
- Document known flaky tests
|
|
1024
|
-
|
|
1025
|
-
### Test Performance Issues
|
|
1026
|
-
|
|
1027
|
-
When tests are slow:
|
|
1028
|
-
- Optimize test setup and teardown
|
|
1029
|
-
- Use test parallelization where possible
|
|
1030
|
-
- Mock expensive operations
|
|
1031
|
-
- Profile slow tests
|
|
1032
|
-
- Consider test prioritization
|
|
1033
|
-
|
|
1034
|
-
For more information about testing your application, see the [Security Guide](./security.md) and [Performance Guide](./performance.md).
|