@jmruthers/pace-core 0.5.68 → 0.5.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-4IUY7BXB.js → DataTable-OSELOGMA.js} +6 -6
- package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-DLpF5bbs.d.ts} +78 -2
- package/dist/{chunk-OPCWH3A4.js → chunk-4YMVZ76F.js} +7 -6
- package/dist/chunk-4YMVZ76F.js.map +1 -0
- package/dist/{chunk-NN45OBIS.js → chunk-5G7JA3L5.js} +3 -5
- package/dist/{chunk-NN45OBIS.js.map → chunk-5G7JA3L5.js.map} +1 -1
- package/dist/{chunk-U6GPOF6J.js → chunk-5NV76BYF.js} +666 -110
- package/dist/chunk-5NV76BYF.js.map +1 -0
- package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
- package/dist/chunk-6RBH67W7.js.map +1 -0
- package/dist/{chunk-ZPG4XPV5.js → chunk-BHBMXMLT.js} +5 -7
- package/dist/chunk-BHBMXMLT.js.map +1 -0
- package/dist/{chunk-ZMS23NS5.js → chunk-FOT3WUV6.js} +3 -5
- package/dist/{chunk-ZMS23NS5.js.map → chunk-FOT3WUV6.js.map} +1 -1
- package/dist/{chunk-MOJXHWDE.js → chunk-GCUIIBLB.js} +382 -5
- package/dist/chunk-GCUIIBLB.js.map +1 -0
- package/dist/{chunk-PXWEDX7Y.js → chunk-KWQH4VO3.js} +3 -3
- package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
- package/dist/chunk-O3NWNXDY.js.map +1 -0
- package/dist/{chunk-KRCRNXPD.js → chunk-OTJUAYBG.js} +81 -18
- package/dist/chunk-OTJUAYBG.js.map +1 -0
- package/dist/chunk-SMJZMKYN.js +141 -0
- package/dist/chunk-SMJZMKYN.js.map +1 -0
- package/dist/{chunk-UYA6U6H7.js → chunk-V2TE7LOF.js} +4 -4
- package/dist/{chunk-L3RV2ALE.js → chunk-VKOCWWVY.js} +6 -1
- package/dist/{chunk-L3RV2ALE.js.map → chunk-VKOCWWVY.js.map} +1 -1
- package/dist/components.d.ts +4 -79
- package/dist/components.js +23 -581
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +9 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +32 -19
- package/dist/index.js.map +1 -1
- package/dist/providers.js +6 -7
- package/dist/rbac/index.js +6 -6
- package/dist/styles/index.js +2 -2
- package/dist/theming/runtime.d.ts +4 -3
- package/dist/theming/runtime.js +3 -1
- package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
- package/dist/utils.d.ts +4 -1
- package/dist/utils.js +3 -3
- package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
- package/docs/README.md +22 -10
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +129 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +7 -7
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +2 -2
- package/docs/api/interfaces/FileDisplayProps.md +107 -0
- package/docs/api/interfaces/FileMetadata.md +129 -0
- package/docs/api/interfaces/FileReference.md +118 -0
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +85 -0
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +228 -23
- package/docs/architecture/services.md +374 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/testing.md +1 -1
- package/docs/breaking-changes.md +182 -0
- package/docs/common-patterns.md +445 -0
- package/docs/core-concepts/authentication.md +26 -11
- package/docs/core-concepts/events.md +2 -0
- package/docs/core-concepts/organisations.md +2 -0
- package/docs/core-concepts/permissions.md +2 -0
- package/docs/{INDEX.md → documentation-index.md} +26 -38
- package/docs/faq.md +286 -0
- package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
- package/docs/getting-started/installation-guide.md +284 -0
- package/docs/getting-started/quick-start.md +8 -1
- package/docs/implementation-guides/app-layout.md +3 -1
- package/docs/implementation-guides/data-tables.md +2 -0
- package/docs/implementation-guides/dynamic-colors.md +47 -2
- package/docs/implementation-guides/event-theming-summary.md +220 -0
- package/docs/implementation-guides/forms.md +9 -7
- package/docs/implementation-guides/navigation.md +2 -0
- package/docs/migration/service-architecture.md +351 -0
- package/docs/rbac/README-rbac-rls-integration.md +2 -2
- package/docs/rbac/README.md +1 -1
- package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
- package/docs/rbac/quick-start.md +2 -0
- package/docs/rbac/rbac-rls-integration.md +2 -2
- package/docs/style-guide.md +136 -1
- package/docs/testing/README.md +1 -1
- package/docs/troubleshooting/authentication-issues.md +334 -0
- package/docs/troubleshooting/common-issues.md +2 -0
- package/docs/troubleshooting/styling-issues.md +199 -144
- package/docs/usage.md +23 -2
- package/package.json +3 -2
- package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
- package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
- package/src/__tests__/fixtures/test-data.ts +90 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
- package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
- package/src/__tests__/helpers/component-test-utils.tsx +14 -4
- package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
- package/src/__tests__/helpers/test-providers.tsx +329 -0
- package/src/__tests__/helpers/test-utils.tsx +91 -45
- package/src/__tests__/helpers/timer-utils.ts +71 -0
- package/src/__tests__/hooks/usePermissions.test.ts +1 -5
- package/src/__tests__/integration/UserProfile.test.tsx +1 -5
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
- package/src/__tests__/setup.ts +34 -28
- package/src/components/Alert/Alert.test.tsx +1 -5
- package/src/components/Avatar/Avatar.test.tsx +1 -5
- package/src/components/Button/Button.test.tsx +4 -20
- package/src/components/Card/Card.test.tsx +1 -5
- package/src/components/Checkbox/Checkbox.test.tsx +1 -5
- package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
- package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
- package/src/components/DataTable/__tests__/styles.test.ts +382 -0
- package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
- package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
- package/src/components/Footer/Footer.test.tsx +1 -5
- package/src/components/Form/Form.test.tsx +22 -35
- package/src/components/Header/Header.test.tsx +1 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
- package/src/components/Input/Input.test.tsx +2 -10
- package/src/components/LoginForm/LoginForm.test.tsx +1 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -4
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
- package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
- package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
- package/src/components/Select/Select.bug-test.tsx +69 -0
- package/src/components/Select/Select.refactored.tsx +497 -0
- package/src/components/Select/Select.test.tsx +42 -49
- package/src/components/Select/Select.tsx +5 -2
- package/src/components/Select/hooks.ts +254 -0
- package/src/components/Switch/Switch.test.tsx +1 -5
- package/src/components/Table/__tests__/Table.test.tsx +775 -0
- package/src/components/Toast/Toast.test.tsx +15 -8
- package/src/components/Tooltip/Tooltip.test.tsx +1 -5
- package/src/components/UserMenu/UserMenu.test.tsx +3 -15
- package/src/components/__tests__/FileDisplay.test.tsx +575 -0
- package/src/components/__tests__/FileUpload.test.tsx +446 -0
- package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
- package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
- package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
- package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
- package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
- package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/public/usePublicEvent.ts +30 -9
- package/src/hooks/public/usePublicRouteParams.ts +13 -3
- package/src/hooks/services/useAuth.ts +50 -0
- package/src/hooks/services/useAuthService.ts +30 -0
- package/src/hooks/services/useCurrentEvent.ts +36 -0
- package/src/hooks/services/useCurrentOrganisation.ts +52 -0
- package/src/hooks/services/useEventService.ts +30 -0
- package/src/hooks/services/useInactivityService.ts +30 -0
- package/src/hooks/services/useOrganisationService.ts +30 -0
- package/src/hooks/services/usePermissions.ts +70 -0
- package/src/hooks/services/useRBACService.ts +30 -0
- package/src/hooks/useCounter.test.ts +1 -5
- package/src/hooks/useEventTheme.ts +86 -0
- package/src/hooks/useOrganisationPermissions.test.ts +2 -5
- package/src/hooks/useOrganisationSecurity.test.ts +1 -5
- package/src/hooks/usePermissionCache.test.ts +1 -5
- package/src/hooks/usePermissionCheck.ts +150 -0
- package/src/hooks/useSecureDataAccess.test.ts +1 -5
- package/src/index.ts +7 -0
- package/src/providers/EventProvider.tsx +58 -2
- package/src/providers/OrganisationProvider.test.tsx +1 -5
- package/src/providers/OrganisationProvider.tsx +56 -4
- package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
- package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
- package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
- package/src/providers/__tests__/EventProvider.test.tsx +211 -110
- package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
- package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
- package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
- package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
- package/src/providers/services/AuthServiceProvider.tsx +65 -0
- package/src/providers/services/EventServiceProvider.tsx +83 -0
- package/src/providers/services/InactivityServiceProvider.tsx +83 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
- package/src/providers/services/RBACServiceProvider.tsx +79 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
- package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
- package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
- package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
- package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
- package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
- package/src/rbac/api.test.ts +1 -9
- package/src/rbac/cache.test.ts +10 -8
- package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
- package/src/rbac/components/NavigationProvider.test.tsx +1 -5
- package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
- package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
- package/src/rbac/config.test.ts +1 -5
- package/src/rbac/hooks/useCan.test.ts +262 -9
- package/src/rbac/hooks/usePermissions.test.ts +246 -6
- package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.test.ts +472 -198
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
- package/src/services/AuthService.ts +416 -0
- package/src/services/EventService.ts +366 -0
- package/src/services/InactivityService.ts +388 -0
- package/src/services/OrganisationService.ts +592 -0
- package/src/services/RBACService.ts +522 -0
- package/src/services/__tests__/AuthService.test.ts +356 -0
- package/src/services/__tests__/BaseService.test.ts +314 -0
- package/src/services/__tests__/EventService.test.ts +489 -0
- package/src/services/__tests__/InactivityService.test.ts +403 -0
- package/src/services/__tests__/OrganisationService.test.ts +660 -0
- package/src/services/__tests__/RBACService.test.ts +492 -0
- package/src/services/base/BaseService.ts +87 -0
- package/src/services/interfaces/IAuthService.ts +39 -0
- package/src/services/interfaces/IEventService.ts +30 -0
- package/src/services/interfaces/IInactivityService.ts +31 -0
- package/src/services/interfaces/IOrganisationService.ts +41 -0
- package/src/services/interfaces/IRBACService.ts +62 -0
- package/src/theming/__tests__/runtime.test.ts +560 -0
- package/src/theming/runtime.ts +71 -28
- package/src/types/__tests__/file-reference.test.ts +447 -0
- package/src/types/__tests__/organisation.test.ts +1133 -0
- package/src/types/__tests__/theme.test.ts +830 -0
- package/src/types/__tests__/type-validation.test.ts +527 -0
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
- package/src/utils/__tests__/debugLogger.test.ts +417 -0
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
- package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
- package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
- package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
- package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
- package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
- package/src/utils/appIdResolver.test.ts +6 -10
- package/src/utils/appNameResolver.simple.test.ts +142 -0
- package/src/utils/appNameResolver.test.ts +31 -458
- package/src/utils/appNameResolver.test.ts.backup +494 -0
- package/src/utils/debugLogger.ts +26 -5
- package/src/utils/formatDate.test.ts +1 -5
- package/src/utils/organisationContext.test.ts +1 -5
- package/src/utils/performanceBudgets.ts +3 -4
- package/src/utils/secureDataAccess.test.ts +1 -5
- package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
- package/dist/chunk-D7ARGIA3.js.map +0 -1
- package/dist/chunk-IPCH4YPT.js +0 -315
- package/dist/chunk-IPCH4YPT.js.map +0 -1
- package/dist/chunk-KRCRNXPD.js.map +0 -1
- package/dist/chunk-MOJXHWDE.js.map +0 -1
- package/dist/chunk-N2EUGZRW.js +0 -98
- package/dist/chunk-N2EUGZRW.js.map +0 -1
- package/dist/chunk-OPCWH3A4.js.map +0 -1
- package/dist/chunk-U6GPOF6J.js.map +0 -1
- package/dist/chunk-ZPG4XPV5.js.map +0 -1
- package/dist/chunk-ZPK5656W.js.map +0 -1
- package/docs/getting-started/installation.md +0 -269
- package/src/__tests__/REBUILD_PLAN.md +0 -223
- package/src/styles/base.css +0 -208
- package/src/styles/semantic.css +0 -24
- /package/dist/{DataTable-4IUY7BXB.js.map → DataTable-OSELOGMA.js.map} +0 -0
- /package/dist/{chunk-PXWEDX7Y.js.map → chunk-KWQH4VO3.js.map} +0 -0
- /package/dist/{chunk-UYA6U6H7.js.map → chunk-V2TE7LOF.js.map} +0 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file AuthService Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Services/__tests__
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Unit tests for AuthService class.
|
|
8
|
+
* Tests authentication operations, state management, and error handling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
12
|
+
import { AuthService } from '../AuthService';
|
|
13
|
+
import { AuthError } from '@supabase/supabase-js';
|
|
14
|
+
|
|
15
|
+
// Mock Supabase client
|
|
16
|
+
const createMockSupabaseClient = () => ({
|
|
17
|
+
auth: {
|
|
18
|
+
signInWithPassword: vi.fn(),
|
|
19
|
+
signUp: vi.fn(),
|
|
20
|
+
signOut: vi.fn(),
|
|
21
|
+
resetPasswordForEmail: vi.fn(),
|
|
22
|
+
updateUser: vi.fn(),
|
|
23
|
+
refreshSession: vi.fn(),
|
|
24
|
+
getSession: vi.fn(),
|
|
25
|
+
getUser: vi.fn(),
|
|
26
|
+
onAuthStateChange: vi.fn(),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('AuthService', () => {
|
|
31
|
+
let mockSupabase: ReturnType<typeof createMockSupabaseClient>;
|
|
32
|
+
let authService: AuthService;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
mockSupabase = createMockSupabaseClient();
|
|
36
|
+
authService = new AuthService(mockSupabase as any);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
authService.cleanup();
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('Initialization', () => {
|
|
45
|
+
it('should initialize with default state', () => {
|
|
46
|
+
expect(authService.getUser()).toBeNull();
|
|
47
|
+
expect(authService.getSession()).toBeNull();
|
|
48
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
49
|
+
expect(authService.isLoading()).toBe(true);
|
|
50
|
+
expect(authService.getError()).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should initialize service when created', async () => {
|
|
54
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
55
|
+
data: { session: null },
|
|
56
|
+
error: null
|
|
57
|
+
});
|
|
58
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
59
|
+
data: { user: null },
|
|
60
|
+
error: null
|
|
61
|
+
});
|
|
62
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
63
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await authService.initialize();
|
|
67
|
+
|
|
68
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
69
|
+
expect(mockSupabase.auth.onAuthStateChange).toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Sign In', () => {
|
|
74
|
+
it('should sign in user successfully', async () => {
|
|
75
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
76
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
77
|
+
|
|
78
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
79
|
+
data: { user: mockUser, session: mockSession },
|
|
80
|
+
error: null
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
84
|
+
|
|
85
|
+
expect(result.user).toEqual(mockUser);
|
|
86
|
+
expect(result.session).toEqual(mockSession);
|
|
87
|
+
expect(result.error).toBeNull();
|
|
88
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
89
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
90
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle sign in errors', async () => {
|
|
94
|
+
const mockError = new AuthError('Invalid credentials');
|
|
95
|
+
|
|
96
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
97
|
+
data: { user: null, session: null },
|
|
98
|
+
error: mockError
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await authService.signIn('test@example.com', 'wrongpassword');
|
|
102
|
+
|
|
103
|
+
expect(result.user).toBeNull();
|
|
104
|
+
expect(result.session).toBeNull();
|
|
105
|
+
expect(result.error).toEqual(mockError);
|
|
106
|
+
expect(authService.getError()).toEqual(mockError);
|
|
107
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle missing Supabase client', async () => {
|
|
111
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
112
|
+
|
|
113
|
+
const result = await serviceWithoutClient.signIn('test@example.com', 'password');
|
|
114
|
+
|
|
115
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
116
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Sign Up', () => {
|
|
121
|
+
it('should sign up user successfully', async () => {
|
|
122
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
123
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
124
|
+
|
|
125
|
+
mockSupabase.auth.signUp.mockResolvedValue({
|
|
126
|
+
data: { user: mockUser, session: mockSession },
|
|
127
|
+
error: null
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
131
|
+
|
|
132
|
+
expect(result.user).toEqual(mockUser);
|
|
133
|
+
expect(result.session).toEqual(mockSession);
|
|
134
|
+
expect(result.error).toBeNull();
|
|
135
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
136
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
137
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle sign up errors', async () => {
|
|
141
|
+
const mockError = new AuthError('Email already registered');
|
|
142
|
+
|
|
143
|
+
mockSupabase.auth.signUp.mockResolvedValue({
|
|
144
|
+
data: { user: null, session: null },
|
|
145
|
+
error: mockError
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
149
|
+
|
|
150
|
+
expect(result.user).toBeNull();
|
|
151
|
+
expect(result.session).toBeNull();
|
|
152
|
+
expect(result.error).toEqual(mockError);
|
|
153
|
+
expect(authService.getError()).toEqual(mockError);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Sign Out', () => {
|
|
158
|
+
it('should sign out user successfully', async () => {
|
|
159
|
+
// First sign in a user
|
|
160
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
161
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
162
|
+
|
|
163
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
164
|
+
data: { user: mockUser, session: mockSession },
|
|
165
|
+
error: null
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await authService.signIn('test@example.com', 'password');
|
|
169
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
170
|
+
|
|
171
|
+
// Then sign out
|
|
172
|
+
mockSupabase.auth.signOut.mockResolvedValue({
|
|
173
|
+
error: null
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const result = await authService.signOut();
|
|
177
|
+
|
|
178
|
+
expect(result.error).toBeNull();
|
|
179
|
+
expect(authService.getUser()).toBeNull();
|
|
180
|
+
expect(authService.getSession()).toBeNull();
|
|
181
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle sign out errors', async () => {
|
|
185
|
+
const mockError = new AuthError('Sign out failed');
|
|
186
|
+
|
|
187
|
+
mockSupabase.auth.signOut.mockResolvedValue({
|
|
188
|
+
error: mockError
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await authService.signOut();
|
|
192
|
+
|
|
193
|
+
expect(result.error).toEqual(mockError);
|
|
194
|
+
expect(authService.getError()).toEqual(mockError);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('Password Reset', () => {
|
|
199
|
+
it('should reset password successfully', async () => {
|
|
200
|
+
mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
|
|
201
|
+
error: null
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = await authService.resetPassword('test@example.com');
|
|
205
|
+
|
|
206
|
+
expect(result.error).toBeNull();
|
|
207
|
+
expect(mockSupabase.auth.resetPasswordForEmail).toHaveBeenCalledWith('test@example.com');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle password reset errors', async () => {
|
|
211
|
+
const mockError = new AuthError('Email not found');
|
|
212
|
+
|
|
213
|
+
mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
|
|
214
|
+
error: mockError
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await authService.resetPassword('test@example.com');
|
|
218
|
+
|
|
219
|
+
expect(result.error).toEqual(mockError);
|
|
220
|
+
expect(authService.getError()).toEqual(mockError);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('Update Password', () => {
|
|
225
|
+
it('should update password successfully', async () => {
|
|
226
|
+
mockSupabase.auth.updateUser.mockResolvedValue({
|
|
227
|
+
error: null
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = await authService.updatePassword('newpassword');
|
|
231
|
+
|
|
232
|
+
expect(result.error).toBeNull();
|
|
233
|
+
expect(mockSupabase.auth.updateUser).toHaveBeenCalledWith({
|
|
234
|
+
password: 'newpassword'
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle update password errors', async () => {
|
|
239
|
+
const mockError = new AuthError('Password too weak');
|
|
240
|
+
|
|
241
|
+
mockSupabase.auth.updateUser.mockResolvedValue({
|
|
242
|
+
error: mockError
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const result = await authService.updatePassword('weak');
|
|
246
|
+
|
|
247
|
+
expect(result.error).toEqual(mockError);
|
|
248
|
+
expect(authService.getError()).toEqual(mockError);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('Refresh Session', () => {
|
|
253
|
+
it('should refresh session successfully', async () => {
|
|
254
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
255
|
+
error: null
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = await authService.refreshSession();
|
|
259
|
+
|
|
260
|
+
expect(result.error).toBeNull();
|
|
261
|
+
expect(mockSupabase.auth.refreshSession).toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should handle refresh session errors', async () => {
|
|
265
|
+
const mockError = new AuthError('Session expired');
|
|
266
|
+
|
|
267
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
268
|
+
error: mockError
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const result = await authService.refreshSession();
|
|
272
|
+
|
|
273
|
+
expect(result.error).toEqual(mockError);
|
|
274
|
+
expect(authService.getError()).toEqual(mockError);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('State Management', () => {
|
|
279
|
+
it('should notify subscribers when state changes', async () => {
|
|
280
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
281
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
282
|
+
|
|
283
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
284
|
+
data: { user: mockUser, session: mockSession },
|
|
285
|
+
error: null
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const subscriber = vi.fn();
|
|
289
|
+
const unsubscribe = authService.subscribe(subscriber);
|
|
290
|
+
|
|
291
|
+
await authService.signIn('test@example.com', 'password');
|
|
292
|
+
|
|
293
|
+
expect(subscriber).toHaveBeenCalled();
|
|
294
|
+
|
|
295
|
+
unsubscribe();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should cleanup subscriptions on cleanup', () => {
|
|
299
|
+
const subscriber = vi.fn();
|
|
300
|
+
authService.subscribe(subscriber);
|
|
301
|
+
|
|
302
|
+
authService.cleanup();
|
|
303
|
+
|
|
304
|
+
// After cleanup, new state changes shouldn't notify subscribers
|
|
305
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
306
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
307
|
+
|
|
308
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
309
|
+
data: { user: mockUser, session: mockSession },
|
|
310
|
+
error: null
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
authService.signIn('test@example.com', 'password');
|
|
314
|
+
|
|
315
|
+
// Subscriber should not be called after cleanup
|
|
316
|
+
expect(subscriber).not.toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('Error Handling', () => {
|
|
321
|
+
it('should handle network errors', async () => {
|
|
322
|
+
const networkError = new Error('Network error');
|
|
323
|
+
|
|
324
|
+
mockSupabase.auth.signInWithPassword.mockRejectedValue(networkError);
|
|
325
|
+
|
|
326
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
327
|
+
|
|
328
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
329
|
+
expect(result.error?.message).toBe('Network error');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should clear errors on successful operations', async () => {
|
|
333
|
+
// First cause an error
|
|
334
|
+
const mockError = new AuthError('Invalid credentials');
|
|
335
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
336
|
+
data: { user: null, session: null },
|
|
337
|
+
error: mockError
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await authService.signIn('test@example.com', 'wrongpassword');
|
|
341
|
+
expect(authService.getError()).toEqual(mockError);
|
|
342
|
+
|
|
343
|
+
// Then succeed
|
|
344
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
345
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
346
|
+
|
|
347
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
348
|
+
data: { user: mockUser, session: mockSession },
|
|
349
|
+
error: null
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await authService.signIn('test@example.com', 'password');
|
|
353
|
+
expect(authService.getError()).toBeNull();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BaseService Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Services/__tests__
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Unit tests for BaseService class.
|
|
8
|
+
* Tests the observable pattern implementation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
12
|
+
import { BaseService } from '../base/BaseService';
|
|
13
|
+
|
|
14
|
+
// Create a concrete implementation of BaseService for testing
|
|
15
|
+
class TestService extends BaseService {
|
|
16
|
+
private _value = 0;
|
|
17
|
+
|
|
18
|
+
getValue(): number {
|
|
19
|
+
return this._value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setValue(value: number): void {
|
|
23
|
+
this._value = value;
|
|
24
|
+
this.notify(); // Trigger notification
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Expose protected method for testing
|
|
28
|
+
testNotify(): void {
|
|
29
|
+
this.notify();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Implement abstract methods
|
|
33
|
+
protected async doInitialize(): Promise<void> {
|
|
34
|
+
// No initialization needed for test
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected doCleanup(): void {
|
|
38
|
+
// No cleanup needed for test
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('BaseService', () => {
|
|
43
|
+
let testService: TestService;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
testService = new TestService();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Observable Pattern', () => {
|
|
50
|
+
it('should subscribe to state changes', () => {
|
|
51
|
+
const listener = vi.fn();
|
|
52
|
+
const unsubscribe = testService.subscribe(listener);
|
|
53
|
+
|
|
54
|
+
testService.setValue(42);
|
|
55
|
+
|
|
56
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
57
|
+
|
|
58
|
+
unsubscribe();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should unsubscribe from state changes', () => {
|
|
62
|
+
const listener = vi.fn();
|
|
63
|
+
const unsubscribe = testService.subscribe(listener);
|
|
64
|
+
|
|
65
|
+
testService.setValue(42);
|
|
66
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
67
|
+
|
|
68
|
+
unsubscribe();
|
|
69
|
+
testService.setValue(100);
|
|
70
|
+
|
|
71
|
+
expect(listener).toHaveBeenCalledTimes(1); // Should not be called after unsubscribe
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should handle multiple subscribers', () => {
|
|
75
|
+
const listener1 = vi.fn();
|
|
76
|
+
const listener2 = vi.fn();
|
|
77
|
+
const listener3 = vi.fn();
|
|
78
|
+
|
|
79
|
+
const unsubscribe1 = testService.subscribe(listener1);
|
|
80
|
+
const unsubscribe2 = testService.subscribe(listener2);
|
|
81
|
+
const unsubscribe3 = testService.subscribe(listener3);
|
|
82
|
+
|
|
83
|
+
testService.setValue(42);
|
|
84
|
+
|
|
85
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(listener3).toHaveBeenCalledTimes(1);
|
|
88
|
+
|
|
89
|
+
unsubscribe1();
|
|
90
|
+
unsubscribe2();
|
|
91
|
+
unsubscribe3();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle partial unsubscription', () => {
|
|
95
|
+
const listener1 = vi.fn();
|
|
96
|
+
const listener2 = vi.fn();
|
|
97
|
+
const listener3 = vi.fn();
|
|
98
|
+
|
|
99
|
+
const unsubscribe1 = testService.subscribe(listener1);
|
|
100
|
+
const unsubscribe2 = testService.subscribe(listener2);
|
|
101
|
+
const unsubscribe3 = testService.subscribe(listener3);
|
|
102
|
+
|
|
103
|
+
testService.setValue(42);
|
|
104
|
+
|
|
105
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(listener3).toHaveBeenCalledTimes(1);
|
|
108
|
+
|
|
109
|
+
unsubscribe2(); // Unsubscribe only listener2
|
|
110
|
+
|
|
111
|
+
testService.setValue(100);
|
|
112
|
+
|
|
113
|
+
expect(listener1).toHaveBeenCalledTimes(2);
|
|
114
|
+
expect(listener2).toHaveBeenCalledTimes(1); // Should not be called
|
|
115
|
+
expect(listener3).toHaveBeenCalledTimes(2);
|
|
116
|
+
|
|
117
|
+
unsubscribe1();
|
|
118
|
+
unsubscribe3();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should handle duplicate subscriptions', () => {
|
|
122
|
+
const listener = vi.fn();
|
|
123
|
+
|
|
124
|
+
const unsubscribe1 = testService.subscribe(listener);
|
|
125
|
+
const unsubscribe2 = testService.subscribe(listener); // Same listener
|
|
126
|
+
|
|
127
|
+
testService.setValue(42);
|
|
128
|
+
|
|
129
|
+
expect(listener).toHaveBeenCalledTimes(2); // Called twice for duplicate subscription
|
|
130
|
+
|
|
131
|
+
unsubscribe1();
|
|
132
|
+
unsubscribe2();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle unsubscribe called multiple times', () => {
|
|
136
|
+
const listener = vi.fn();
|
|
137
|
+
const unsubscribe = testService.subscribe(listener);
|
|
138
|
+
|
|
139
|
+
testService.setValue(42);
|
|
140
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
141
|
+
|
|
142
|
+
unsubscribe();
|
|
143
|
+
unsubscribe(); // Call unsubscribe again
|
|
144
|
+
|
|
145
|
+
testService.setValue(100);
|
|
146
|
+
expect(listener).toHaveBeenCalledTimes(1); // Should not be called
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('Cleanup', () => {
|
|
151
|
+
it('should cleanup all subscriptions', () => {
|
|
152
|
+
const listener1 = vi.fn();
|
|
153
|
+
const listener2 = vi.fn();
|
|
154
|
+
const listener3 = vi.fn();
|
|
155
|
+
|
|
156
|
+
testService.subscribe(listener1);
|
|
157
|
+
testService.subscribe(listener2);
|
|
158
|
+
testService.subscribe(listener3);
|
|
159
|
+
|
|
160
|
+
testService.setValue(42);
|
|
161
|
+
|
|
162
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
163
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(listener3).toHaveBeenCalledTimes(1);
|
|
165
|
+
|
|
166
|
+
testService.cleanup();
|
|
167
|
+
|
|
168
|
+
testService.setValue(100);
|
|
169
|
+
|
|
170
|
+
expect(listener1).toHaveBeenCalledTimes(1); // Should not be called after cleanup
|
|
171
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(listener3).toHaveBeenCalledTimes(1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle cleanup when no subscriptions exist', () => {
|
|
176
|
+
// Should not throw error
|
|
177
|
+
expect(() => testService.cleanup()).not.toThrow();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle multiple cleanup calls', () => {
|
|
181
|
+
const listener = vi.fn();
|
|
182
|
+
testService.subscribe(listener);
|
|
183
|
+
|
|
184
|
+
testService.cleanup();
|
|
185
|
+
testService.cleanup(); // Second cleanup call
|
|
186
|
+
|
|
187
|
+
// Should not throw error
|
|
188
|
+
expect(() => testService.cleanup()).not.toThrow();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('Notification', () => {
|
|
193
|
+
it('should notify all subscribers', () => {
|
|
194
|
+
const listener1 = vi.fn();
|
|
195
|
+
const listener2 = vi.fn();
|
|
196
|
+
const listener3 = vi.fn();
|
|
197
|
+
|
|
198
|
+
testService.subscribe(listener1);
|
|
199
|
+
testService.subscribe(listener2);
|
|
200
|
+
testService.subscribe(listener3);
|
|
201
|
+
|
|
202
|
+
testService.testNotify();
|
|
203
|
+
|
|
204
|
+
expect(listener1).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(listener2).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(listener3).toHaveBeenCalledTimes(1);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should handle notification when no subscribers exist', () => {
|
|
210
|
+
// Should not throw error
|
|
211
|
+
expect(() => testService.testNotify()).not.toThrow();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle subscriber errors gracefully', () => {
|
|
215
|
+
const errorListener = vi.fn(() => {
|
|
216
|
+
throw new Error('Subscriber error');
|
|
217
|
+
});
|
|
218
|
+
const normalListener = vi.fn();
|
|
219
|
+
|
|
220
|
+
testService.subscribe(errorListener);
|
|
221
|
+
testService.subscribe(normalListener);
|
|
222
|
+
|
|
223
|
+
// Should not throw error, and normal listener should still be called
|
|
224
|
+
expect(() => testService.testNotify()).not.toThrow();
|
|
225
|
+
expect(normalListener).toHaveBeenCalledTimes(1);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should handle multiple errors in subscribers', () => {
|
|
229
|
+
const errorListener1 = vi.fn(() => {
|
|
230
|
+
throw new Error('Subscriber error 1');
|
|
231
|
+
});
|
|
232
|
+
const errorListener2 = vi.fn(() => {
|
|
233
|
+
throw new Error('Subscriber error 2');
|
|
234
|
+
});
|
|
235
|
+
const normalListener = vi.fn();
|
|
236
|
+
|
|
237
|
+
testService.subscribe(errorListener1);
|
|
238
|
+
testService.subscribe(errorListener2);
|
|
239
|
+
testService.subscribe(normalListener);
|
|
240
|
+
|
|
241
|
+
// Should not throw error, and normal listener should still be called
|
|
242
|
+
expect(() => testService.testNotify()).not.toThrow();
|
|
243
|
+
expect(normalListener).toHaveBeenCalledTimes(1);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('Memory Management', () => {
|
|
248
|
+
it('should not leak memory with many subscriptions', () => {
|
|
249
|
+
const listeners = Array.from({ length: 1000 }, () => vi.fn());
|
|
250
|
+
const unsubscribes = listeners.map(listener => testService.subscribe(listener));
|
|
251
|
+
|
|
252
|
+
testService.setValue(42);
|
|
253
|
+
|
|
254
|
+
// All listeners should be called
|
|
255
|
+
listeners.forEach(listener => {
|
|
256
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Unsubscribe all
|
|
260
|
+
unsubscribes.forEach(unsubscribe => unsubscribe());
|
|
261
|
+
|
|
262
|
+
testService.setValue(100);
|
|
263
|
+
|
|
264
|
+
// No listeners should be called
|
|
265
|
+
listeners.forEach(listener => {
|
|
266
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle rapid subscription/unsubscription', () => {
|
|
271
|
+
const listener = vi.fn();
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < 100; i++) {
|
|
274
|
+
const unsubscribe = testService.subscribe(listener);
|
|
275
|
+
testService.setValue(i);
|
|
276
|
+
unsubscribe();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
expect(listener).toHaveBeenCalledTimes(100);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('Edge Cases', () => {
|
|
284
|
+
it('should handle null listener', () => {
|
|
285
|
+
// Should not throw error
|
|
286
|
+
expect(() => testService.subscribe(null as any)).not.toThrow();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should handle undefined listener', () => {
|
|
290
|
+
// Should not throw error
|
|
291
|
+
expect(() => testService.subscribe(undefined as any)).not.toThrow();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should handle function that returns a value', () => {
|
|
295
|
+
const listener = vi.fn(() => 'return value');
|
|
296
|
+
testService.subscribe(listener);
|
|
297
|
+
|
|
298
|
+
testService.setValue(42);
|
|
299
|
+
|
|
300
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should handle async listener', async () => {
|
|
304
|
+
const listener = vi.fn(async () => {
|
|
305
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
testService.subscribe(listener);
|
|
309
|
+
testService.setValue(42);
|
|
310
|
+
|
|
311
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|