@jmruthers/pace-core 0.5.188 → 0.5.190
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -5
- package/dist/components.js +19 -23
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -12
- package/dist/index.js +79 -73
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.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 +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- 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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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 +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +155 -135
- package/docs/api-reference/components.md +72 -29
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -4
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +252 -226
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +10 -8
- package/src/components/index.ts +2 -1
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/index.ts +2 -1
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-EFCLXK7F.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-IPCH26AG.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UNOTYLQF.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -1,38 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Avatar Component Tests
|
|
3
|
-
* @description Comprehensive tests for Avatar
|
|
3
|
+
* @description Comprehensive tests for Avatar component
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { screen, waitFor } from '@testing-library/react';
|
|
8
8
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
-
import { Avatar
|
|
9
|
+
import { Avatar } from './Avatar';
|
|
10
|
+
import { FileCategory } from '../../types/file-reference';
|
|
10
11
|
import { renderWithProviders, userEvent } from '../../__tests__/helpers/test-utils';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
const mockUseUnifiedAuthValue = {
|
|
14
|
+
supabase: null as Record<string, unknown> | null,
|
|
15
|
+
user: { id: 'test-user' },
|
|
16
|
+
signOut: vi.fn(),
|
|
17
|
+
updatePassword: vi.fn(),
|
|
18
|
+
isAuthenticated: true,
|
|
19
|
+
isLoading: false,
|
|
20
|
+
error: null
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockUseFileReferenceById = vi.fn(() => ({
|
|
24
|
+
fileReference: null,
|
|
25
|
+
fileUrl: null,
|
|
26
|
+
isLoading: false
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
|
|
30
|
+
useUnifiedAuth: () => mockUseUnifiedAuthValue,
|
|
31
|
+
UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('../../hooks/useFileReference', () => ({
|
|
35
|
+
useFileReferenceById: (...args: unknown[]) => mockUseFileReferenceById(...args)
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock FileDisplay component
|
|
39
|
+
vi.mock('../FileDisplay/FileDisplay', () => ({
|
|
40
|
+
FileDisplay: ({ fallbackText, className }: any) => (
|
|
41
|
+
<div data-testid="file-display" className={className}>
|
|
42
|
+
<p>{fallbackText}</p>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Size classes for testing
|
|
48
|
+
const sizeClasses = {
|
|
49
|
+
xs: 'size-4 text-xs',
|
|
50
|
+
sm: 'size-6 text-sm',
|
|
51
|
+
md: 'size-10 text-base',
|
|
52
|
+
lg: 'size-12 text-lg',
|
|
53
|
+
xl: 'size-16 text-xl',
|
|
54
|
+
'2xl': 'size-20 text-2xl'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Helper function to get the avatar container element (outer container, not inner fallback)
|
|
13
58
|
const getAvatarContainer = (fallbackText: string) => {
|
|
14
|
-
|
|
59
|
+
const fallbackNode = screen.getByText(fallbackText);
|
|
60
|
+
const container = fallbackNode.parentElement as HTMLElement | null;
|
|
61
|
+
if (!container) {
|
|
62
|
+
throw new Error('Avatar container not found');
|
|
63
|
+
}
|
|
64
|
+
return container;
|
|
15
65
|
};
|
|
16
66
|
|
|
17
67
|
describe('Avatar Component', () => {
|
|
18
|
-
|
|
19
|
-
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
mockUseUnifiedAuthValue.supabase = null;
|
|
71
|
+
mockUseFileReferenceById.mockReset();
|
|
72
|
+
mockUseFileReferenceById.mockReturnValue({
|
|
73
|
+
fileReference: null,
|
|
74
|
+
fileUrl: null,
|
|
75
|
+
isLoading: false
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('Rendering', () => {
|
|
80
|
+
it('renders with fallback only', () => {
|
|
20
81
|
renderWithProviders(
|
|
21
|
-
<Avatar
|
|
22
|
-
<AvatarFallback>AB</AvatarFallback>
|
|
23
|
-
</Avatar>
|
|
82
|
+
<Avatar fallback="AB" />
|
|
24
83
|
);
|
|
25
84
|
|
|
26
85
|
const avatar = getAvatarContainer('AB');
|
|
27
86
|
expect(avatar).toBeInTheDocument();
|
|
28
|
-
|
|
87
|
+
// Avatar uses Tailwind v4 size-* utility instead of h-* w-*
|
|
88
|
+
expect(avatar).toHaveClass('size-10', 'overflow-hidden', 'rounded-full');
|
|
89
|
+
expect(screen.getByText('AB')).toBeInTheDocument();
|
|
29
90
|
});
|
|
30
91
|
|
|
31
92
|
it('renders with custom className', () => {
|
|
32
93
|
renderWithProviders(
|
|
33
|
-
<Avatar className="custom-avatar-class"
|
|
34
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
35
|
-
</Avatar>
|
|
94
|
+
<Avatar fallback="JD" className="custom-avatar-class" />
|
|
36
95
|
);
|
|
37
96
|
|
|
38
97
|
const avatar = getAvatarContainer('JD');
|
|
@@ -41,32 +100,36 @@ describe('Avatar Component', () => {
|
|
|
41
100
|
|
|
42
101
|
it('renders with custom size via className', () => {
|
|
43
102
|
renderWithProviders(
|
|
44
|
-
<Avatar className="
|
|
45
|
-
<AvatarFallback>LG</AvatarFallback>
|
|
46
|
-
</Avatar>
|
|
103
|
+
<Avatar fallback="LG" className="size-16" />
|
|
47
104
|
);
|
|
48
105
|
|
|
49
106
|
const avatar = getAvatarContainer('LG');
|
|
50
|
-
expect(avatar).toHaveClass('
|
|
107
|
+
expect(avatar).toHaveClass('size-16');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('renders with size prop', () => {
|
|
111
|
+
renderWithProviders(
|
|
112
|
+
<Avatar fallback="SM" size="sm" />
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const avatar = getAvatarContainer('SM');
|
|
116
|
+
expect(avatar).toHaveClass('size-6', 'text-sm');
|
|
51
117
|
});
|
|
52
118
|
|
|
53
119
|
it('forwards ref correctly', () => {
|
|
54
|
-
const ref = React.createRef<
|
|
120
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
55
121
|
|
|
56
122
|
renderWithProviders(
|
|
57
|
-
<Avatar ref={ref}
|
|
58
|
-
<AvatarFallback>RF</AvatarFallback>
|
|
59
|
-
</Avatar>
|
|
123
|
+
<Avatar ref={ref} fallback="RF" />
|
|
60
124
|
);
|
|
61
125
|
|
|
62
|
-
|
|
126
|
+
// Avatar uses figure element, not div
|
|
127
|
+
expect(ref.current).toBeInstanceOf(HTMLElement);
|
|
63
128
|
});
|
|
64
129
|
|
|
65
130
|
it('accepts HTML attributes', () => {
|
|
66
131
|
renderWithProviders(
|
|
67
|
-
<Avatar data-testid="avatar" aria-label="User avatar"
|
|
68
|
-
<AvatarFallback>AT</AvatarFallback>
|
|
69
|
-
</Avatar>
|
|
132
|
+
<Avatar data-testid="avatar" aria-label="User avatar" fallback="AT" />
|
|
70
133
|
);
|
|
71
134
|
|
|
72
135
|
const avatar = screen.getByTestId('avatar');
|
|
@@ -74,138 +137,128 @@ describe('Avatar Component', () => {
|
|
|
74
137
|
});
|
|
75
138
|
});
|
|
76
139
|
|
|
77
|
-
describe('
|
|
78
|
-
it('renders with src and alt attributes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Object.defineProperty(global, 'Image', {
|
|
90
|
-
value: vi.fn(() => {
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
if (mockImage.onload) mockImage.onload();
|
|
93
|
-
}, 0);
|
|
94
|
-
return mockImage;
|
|
95
|
-
}),
|
|
96
|
-
writable: true,
|
|
97
|
-
});
|
|
140
|
+
describe('Direct URL Approach', () => {
|
|
141
|
+
it('renders image with src and alt attributes', () => {
|
|
142
|
+
renderWithProviders(
|
|
143
|
+
<Avatar src="/user.jpg" alt="User profile" fallback="UI" />
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const image = screen.getByAltText('User profile');
|
|
147
|
+
expect(image).toBeInTheDocument();
|
|
148
|
+
expect(image).toHaveAttribute('src', '/user.jpg');
|
|
149
|
+
expect(image).toHaveAttribute('alt', 'User profile');
|
|
150
|
+
expect(image.tagName).toBe('IMG');
|
|
151
|
+
});
|
|
98
152
|
|
|
153
|
+
it('shows fallback when image fails to load', async () => {
|
|
99
154
|
renderWithProviders(
|
|
100
|
-
<Avatar
|
|
101
|
-
<AvatarImage src="/user.jpg" alt="User profile" />
|
|
102
|
-
<AvatarFallback>UI</AvatarFallback>
|
|
103
|
-
</Avatar>
|
|
155
|
+
<Avatar src="/broken-image.jpg" alt="User" fallback="FL" />
|
|
104
156
|
);
|
|
105
157
|
|
|
106
|
-
|
|
107
|
-
expect(
|
|
158
|
+
const image = screen.getByAltText('User');
|
|
159
|
+
expect(image).toBeInTheDocument();
|
|
160
|
+
|
|
161
|
+
// Simulate image error
|
|
162
|
+
const errorEvent = new Event('error');
|
|
163
|
+
image.dispatchEvent(errorEvent);
|
|
108
164
|
|
|
109
|
-
// Wait for image to load
|
|
110
165
|
await waitFor(() => {
|
|
111
|
-
|
|
112
|
-
if (image) {
|
|
113
|
-
expect(image).toHaveAttribute('src', '/user.jpg');
|
|
114
|
-
expect(image).toHaveAttribute('alt', 'User profile');
|
|
115
|
-
}
|
|
166
|
+
expect(screen.getByText('FL')).toBeInTheDocument();
|
|
116
167
|
});
|
|
117
168
|
});
|
|
118
169
|
|
|
119
|
-
it('
|
|
170
|
+
it('uses fallback as alt text when alt not provided', () => {
|
|
120
171
|
renderWithProviders(
|
|
121
|
-
<Avatar
|
|
122
|
-
<AvatarImage src="/broken-image.jpg" alt="User" />
|
|
123
|
-
<AvatarFallback>FL</AvatarFallback>
|
|
124
|
-
</Avatar>
|
|
172
|
+
<Avatar src="/user.jpg" fallback="JD" />
|
|
125
173
|
);
|
|
126
174
|
|
|
127
|
-
|
|
128
|
-
expect(
|
|
129
|
-
expect(screen.queryByAltText('User')).not.toBeInTheDocument();
|
|
175
|
+
const image = screen.getByAltText('JD');
|
|
176
|
+
expect(image).toBeInTheDocument();
|
|
130
177
|
});
|
|
131
178
|
|
|
132
|
-
it('
|
|
179
|
+
it('applies container and image classes correctly', () => {
|
|
133
180
|
renderWithProviders(
|
|
134
|
-
<Avatar
|
|
135
|
-
<AvatarImage alt="User" />
|
|
136
|
-
<AvatarFallback>MS</AvatarFallback>
|
|
137
|
-
</Avatar>
|
|
181
|
+
<Avatar src="/user.jpg" alt="User" fallback="IC" className="custom-img-class" />
|
|
138
182
|
);
|
|
139
183
|
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
const image = screen.getByAltText('User');
|
|
185
|
+
// className is applied to container, not image. Image only gets base image classes
|
|
186
|
+
expect(image).toHaveClass('object-cover', 'size-full');
|
|
187
|
+
// Container should have the custom className
|
|
188
|
+
const container = image.parentElement;
|
|
189
|
+
expect(container).toHaveClass('custom-img-class');
|
|
142
190
|
});
|
|
191
|
+
});
|
|
143
192
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
193
|
+
describe('File Reference Props Approach', () => {
|
|
194
|
+
it('renders FileDisplay when file props provided', () => {
|
|
195
|
+
const { container } = renderWithProviders(
|
|
196
|
+
<Avatar
|
|
197
|
+
table_name="user_profiles"
|
|
198
|
+
record_id="user-123"
|
|
199
|
+
organisation_id="org-123"
|
|
200
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
201
|
+
fallback="FP"
|
|
202
|
+
/>
|
|
152
203
|
);
|
|
153
204
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
expect(
|
|
205
|
+
// FileDisplay should be rendered (it will show fallback if no file exists)
|
|
206
|
+
const avatar = getAvatarContainer('FP');
|
|
207
|
+
expect(avatar).toBeInTheDocument();
|
|
157
208
|
});
|
|
158
209
|
|
|
159
|
-
it('
|
|
210
|
+
it('requires all file props for file reference approach', () => {
|
|
160
211
|
renderWithProviders(
|
|
161
|
-
<Avatar
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Check if image element exists with attributes
|
|
173
|
-
const image = screen.queryByTestId('avatar-image');
|
|
174
|
-
if (image) {
|
|
175
|
-
expect(image).toHaveAttribute('loading', 'lazy');
|
|
176
|
-
}
|
|
212
|
+
<Avatar
|
|
213
|
+
table_name="user_profiles"
|
|
214
|
+
record_id="user-123"
|
|
215
|
+
organisation_id="org-123"
|
|
216
|
+
// category missing
|
|
217
|
+
fallback="MP"
|
|
218
|
+
/>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Should show fallback when props incomplete
|
|
222
|
+
expect(screen.getByText('MP')).toBeInTheDocument();
|
|
177
223
|
});
|
|
178
224
|
});
|
|
179
225
|
|
|
180
|
-
describe('
|
|
181
|
-
it('
|
|
226
|
+
describe('File ID Approach', () => {
|
|
227
|
+
it('shows fallback when fileId provided but no supabase', () => {
|
|
182
228
|
renderWithProviders(
|
|
183
|
-
<Avatar
|
|
184
|
-
|
|
185
|
-
|
|
229
|
+
<Avatar
|
|
230
|
+
fileId="file-123"
|
|
231
|
+
organisation_id="org-123"
|
|
232
|
+
fallback="FI"
|
|
233
|
+
/>
|
|
186
234
|
);
|
|
187
235
|
|
|
188
|
-
|
|
189
|
-
expect(
|
|
190
|
-
expect(fallback).toHaveClass('flex', 'h-full', 'w-full', 'items-center', 'justify-center', 'rounded-full', 'text-sec-50', 'bg-sec-500');
|
|
236
|
+
// Should show fallback when supabase not available
|
|
237
|
+
expect(screen.getByText('FI')).toBeInTheDocument();
|
|
191
238
|
});
|
|
239
|
+
});
|
|
192
240
|
|
|
193
|
-
|
|
241
|
+
describe('Fallback Display', () => {
|
|
242
|
+
it('renders fallback with correct styling', () => {
|
|
194
243
|
renderWithProviders(
|
|
195
|
-
<Avatar
|
|
196
|
-
<AvatarFallback className="custom-fallback-class">CF</AvatarFallback>
|
|
197
|
-
</Avatar>
|
|
244
|
+
<Avatar fallback="AB" />
|
|
198
245
|
);
|
|
199
246
|
|
|
200
|
-
const fallback = screen.getByText('
|
|
201
|
-
expect(fallback).
|
|
247
|
+
const fallback = screen.getByText('AB');
|
|
248
|
+
expect(fallback).toBeInTheDocument();
|
|
249
|
+
expect(fallback).toHaveClass(
|
|
250
|
+
'size-full',
|
|
251
|
+
'grid',
|
|
252
|
+
'place-items-center',
|
|
253
|
+
'text-center',
|
|
254
|
+
'text-sec-50',
|
|
255
|
+
'bg-sec-500'
|
|
256
|
+
);
|
|
202
257
|
});
|
|
203
258
|
|
|
204
259
|
it('renders with initials', () => {
|
|
205
260
|
renderWithProviders(
|
|
206
|
-
<Avatar
|
|
207
|
-
<AvatarFallback>John Doe</AvatarFallback>
|
|
208
|
-
</Avatar>
|
|
261
|
+
<Avatar fallback="John Doe" />
|
|
209
262
|
);
|
|
210
263
|
|
|
211
264
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
@@ -213,90 +266,49 @@ describe('Avatar Component', () => {
|
|
|
213
266
|
|
|
214
267
|
it('renders with single character', () => {
|
|
215
268
|
renderWithProviders(
|
|
216
|
-
<Avatar
|
|
217
|
-
<AvatarFallback>J</AvatarFallback>
|
|
218
|
-
</Avatar>
|
|
269
|
+
<Avatar fallback="J" />
|
|
219
270
|
);
|
|
220
271
|
|
|
221
272
|
expect(screen.getByText('J')).toBeInTheDocument();
|
|
222
273
|
});
|
|
223
274
|
|
|
224
|
-
it('
|
|
225
|
-
const ref = React.createRef<HTMLSpanElement>();
|
|
226
|
-
|
|
275
|
+
it('has aria-label on fallback', () => {
|
|
227
276
|
renderWithProviders(
|
|
228
|
-
<Avatar
|
|
229
|
-
<AvatarFallback ref={ref}>RF</AvatarFallback>
|
|
230
|
-
</Avatar>
|
|
277
|
+
<Avatar fallback="AB" alt="User avatar" />
|
|
231
278
|
);
|
|
232
279
|
|
|
233
|
-
|
|
280
|
+
const fallback = screen.getByText('AB');
|
|
281
|
+
expect(fallback).toHaveAttribute('aria-label', 'User avatar');
|
|
234
282
|
});
|
|
235
283
|
|
|
236
|
-
it('
|
|
284
|
+
it('uses fallback as aria-label when alt not provided', () => {
|
|
237
285
|
renderWithProviders(
|
|
238
|
-
<Avatar
|
|
239
|
-
<AvatarFallback data-testid="fallback" aria-label="User initials">
|
|
240
|
-
AT
|
|
241
|
-
</AvatarFallback>
|
|
242
|
-
</Avatar>
|
|
286
|
+
<Avatar fallback="JD" />
|
|
243
287
|
);
|
|
244
288
|
|
|
245
|
-
const fallback = screen.
|
|
246
|
-
expect(fallback).toHaveAttribute('aria-label', '
|
|
289
|
+
const fallback = screen.getByText('JD');
|
|
290
|
+
expect(fallback).toHaveAttribute('aria-label', 'JD');
|
|
247
291
|
});
|
|
248
292
|
});
|
|
249
293
|
|
|
250
294
|
describe('Composition', () => {
|
|
251
|
-
it('works with fallback only', () => {
|
|
252
|
-
renderWithProviders(
|
|
253
|
-
<Avatar>
|
|
254
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
255
|
-
</Avatar>
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
const avatar = getAvatarContainer('JD');
|
|
259
|
-
expect(avatar).toBeInTheDocument();
|
|
260
|
-
expect(screen.getByText('JD')).toBeInTheDocument();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
295
|
it('works with multiple avatars', () => {
|
|
264
296
|
renderWithProviders(
|
|
265
297
|
<div>
|
|
266
|
-
<Avatar
|
|
267
|
-
|
|
268
|
-
</Avatar>
|
|
269
|
-
<Avatar>
|
|
270
|
-
<AvatarFallback>CD</AvatarFallback>
|
|
271
|
-
</Avatar>
|
|
298
|
+
<Avatar fallback="AB" />
|
|
299
|
+
<Avatar fallback="CD" />
|
|
272
300
|
</div>
|
|
273
301
|
);
|
|
274
302
|
|
|
275
303
|
expect(screen.getByText('AB')).toBeInTheDocument();
|
|
276
304
|
expect(screen.getByText('CD')).toBeInTheDocument();
|
|
277
305
|
});
|
|
278
|
-
|
|
279
|
-
it('works with complex fallback content', () => {
|
|
280
|
-
renderWithProviders(
|
|
281
|
-
<Avatar>
|
|
282
|
-
<AvatarFallback>
|
|
283
|
-
<span>👤</span>
|
|
284
|
-
<span>User</span>
|
|
285
|
-
</AvatarFallback>
|
|
286
|
-
</Avatar>
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
expect(screen.getByText('👤')).toBeInTheDocument();
|
|
290
|
-
expect(screen.getByText('User')).toBeInTheDocument();
|
|
291
|
-
});
|
|
292
306
|
});
|
|
293
307
|
|
|
294
308
|
describe('Accessibility', () => {
|
|
295
309
|
it('supports screen readers with fallback', () => {
|
|
296
310
|
renderWithProviders(
|
|
297
|
-
<Avatar
|
|
298
|
-
<AvatarFallback>John Doe</AvatarFallback>
|
|
299
|
-
</Avatar>
|
|
311
|
+
<Avatar fallback="John Doe" />
|
|
300
312
|
);
|
|
301
313
|
|
|
302
314
|
const avatar = getAvatarContainer('John Doe');
|
|
@@ -306,9 +318,7 @@ describe('Avatar Component', () => {
|
|
|
306
318
|
|
|
307
319
|
it('maintains focus management', () => {
|
|
308
320
|
renderWithProviders(
|
|
309
|
-
<Avatar tabIndex={0}
|
|
310
|
-
<AvatarFallback>FM</AvatarFallback>
|
|
311
|
-
</Avatar>
|
|
321
|
+
<Avatar tabIndex={0} fallback="FM" />
|
|
312
322
|
);
|
|
313
323
|
|
|
314
324
|
const avatar = getAvatarContainer('FM');
|
|
@@ -319,9 +329,7 @@ describe('Avatar Component', () => {
|
|
|
319
329
|
const user = userEvent.setup();
|
|
320
330
|
|
|
321
331
|
renderWithProviders(
|
|
322
|
-
<Avatar tabIndex={0}
|
|
323
|
-
<AvatarFallback>KN</AvatarFallback>
|
|
324
|
-
</Avatar>
|
|
332
|
+
<Avatar tabIndex={0} fallback="KN" />
|
|
325
333
|
);
|
|
326
334
|
|
|
327
335
|
const avatar = getAvatarContainer('KN');
|
|
@@ -339,22 +347,19 @@ describe('Avatar Component', () => {
|
|
|
339
347
|
describe('Edge Cases', () => {
|
|
340
348
|
it('handles empty fallback content', () => {
|
|
341
349
|
renderWithProviders(
|
|
342
|
-
<Avatar
|
|
343
|
-
<AvatarFallback></AvatarFallback>
|
|
344
|
-
</Avatar>
|
|
350
|
+
<Avatar fallback="" />
|
|
345
351
|
);
|
|
346
352
|
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
const
|
|
350
|
-
expect(
|
|
353
|
+
// Avatar container should exist even with empty fallback
|
|
354
|
+
// Avatar uses figure element, not div
|
|
355
|
+
const fallback = document.querySelector('[aria-label=""]') as HTMLElement | null;
|
|
356
|
+
expect(fallback).not.toBeNull();
|
|
357
|
+
expect(fallback?.parentElement).toBeInstanceOf(HTMLElement);
|
|
351
358
|
});
|
|
352
359
|
|
|
353
360
|
it('handles very long fallback text', () => {
|
|
354
361
|
renderWithProviders(
|
|
355
|
-
<Avatar
|
|
356
|
-
<AvatarFallback>Very Long Username That Might Overflow</AvatarFallback>
|
|
357
|
-
</Avatar>
|
|
362
|
+
<Avatar fallback="Very Long Username That Might Overflow" />
|
|
358
363
|
);
|
|
359
364
|
|
|
360
365
|
expect(screen.getByText('Very Long Username That Might Overflow')).toBeInTheDocument();
|
|
@@ -362,9 +367,7 @@ describe('Avatar Component', () => {
|
|
|
362
367
|
|
|
363
368
|
it('handles special characters in fallback', () => {
|
|
364
369
|
renderWithProviders(
|
|
365
|
-
<Avatar
|
|
366
|
-
<AvatarFallback>@#$%</AvatarFallback>
|
|
367
|
-
</Avatar>
|
|
370
|
+
<Avatar fallback="@#$%" />
|
|
368
371
|
);
|
|
369
372
|
|
|
370
373
|
expect(screen.getByText('@#$%')).toBeInTheDocument();
|
|
@@ -372,23 +375,42 @@ describe('Avatar Component', () => {
|
|
|
372
375
|
|
|
373
376
|
it('handles numeric fallback content', () => {
|
|
374
377
|
renderWithProviders(
|
|
375
|
-
<Avatar
|
|
376
|
-
<AvatarFallback>123</AvatarFallback>
|
|
377
|
-
</Avatar>
|
|
378
|
+
<Avatar fallback="123" />
|
|
378
379
|
);
|
|
379
380
|
|
|
380
381
|
expect(screen.getByText('123')).toBeInTheDocument();
|
|
381
382
|
});
|
|
383
|
+
|
|
384
|
+
it('prioritizes file props over direct URL', () => {
|
|
385
|
+
// Provide mock supabase so file props can be used
|
|
386
|
+
mockUseUnifiedAuthValue.supabase = {} as any;
|
|
387
|
+
|
|
388
|
+
renderWithProviders(
|
|
389
|
+
<Avatar
|
|
390
|
+
src="/user.jpg"
|
|
391
|
+
alt="User"
|
|
392
|
+
table_name="user_profiles"
|
|
393
|
+
record_id="user-123"
|
|
394
|
+
organisation_id="org-123"
|
|
395
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
396
|
+
fallback="PR"
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// File props take priority over direct URL, so should show FileDisplay
|
|
401
|
+
// FileDisplay will render fallback when file doesn't exist in test
|
|
402
|
+
// The fallback text "PR" should be visible
|
|
403
|
+
expect(screen.getByText('PR')).toBeInTheDocument();
|
|
404
|
+
// Since file props take priority, direct URL image should not be rendered
|
|
405
|
+
expect(screen.queryByAltText('User')).not.toBeInTheDocument();
|
|
406
|
+
});
|
|
382
407
|
});
|
|
383
408
|
|
|
384
409
|
describe('Integration', () => {
|
|
385
410
|
it('works within user profile components', () => {
|
|
386
411
|
renderWithProviders(
|
|
387
412
|
<div className="user-profile">
|
|
388
|
-
<Avatar
|
|
389
|
-
<AvatarImage src="/user.jpg" alt="Profile" />
|
|
390
|
-
<AvatarFallback>UP</AvatarFallback>
|
|
391
|
-
</Avatar>
|
|
413
|
+
<Avatar src="/user.jpg" alt="Profile" fallback="UP" />
|
|
392
414
|
<div>
|
|
393
415
|
<h3>User Name</h3>
|
|
394
416
|
<p>User description</p>
|
|
@@ -396,7 +418,7 @@ describe('Avatar Component', () => {
|
|
|
396
418
|
</div>
|
|
397
419
|
);
|
|
398
420
|
|
|
399
|
-
expect(screen.
|
|
421
|
+
expect(screen.getByAltText('Profile')).toBeInTheDocument();
|
|
400
422
|
expect(screen.getByText('User Name')).toBeInTheDocument();
|
|
401
423
|
expect(screen.getByText('User description')).toBeInTheDocument();
|
|
402
424
|
});
|
|
@@ -404,9 +426,7 @@ describe('Avatar Component', () => {
|
|
|
404
426
|
it('works within navigation menus', () => {
|
|
405
427
|
renderWithProviders(
|
|
406
428
|
<nav>
|
|
407
|
-
<Avatar
|
|
408
|
-
<AvatarFallback>NM</AvatarFallback>
|
|
409
|
-
</Avatar>
|
|
429
|
+
<Avatar fallback="NM" />
|
|
410
430
|
<ul>
|
|
411
431
|
<li>Dashboard</li>
|
|
412
432
|
<li>Settings</li>
|
|
@@ -424,9 +444,7 @@ describe('Avatar Component', () => {
|
|
|
424
444
|
const handleClick = vi.fn();
|
|
425
445
|
|
|
426
446
|
renderWithProviders(
|
|
427
|
-
<Avatar onClick={handleClick} tabIndex={0}
|
|
428
|
-
<AvatarFallback>CH</AvatarFallback>
|
|
429
|
-
</Avatar>
|
|
447
|
+
<Avatar onClick={handleClick} tabIndex={0} fallback="CH" />
|
|
430
448
|
);
|
|
431
449
|
|
|
432
450
|
const avatar = getAvatarContainer('CH');
|
|
@@ -440,18 +458,13 @@ describe('Avatar Component', () => {
|
|
|
440
458
|
describe('Styling and Layout', () => {
|
|
441
459
|
it('applies correct default styling', () => {
|
|
442
460
|
renderWithProviders(
|
|
443
|
-
<Avatar
|
|
444
|
-
<AvatarFallback>ST</AvatarFallback>
|
|
445
|
-
</Avatar>
|
|
461
|
+
<Avatar fallback="ST" />
|
|
446
462
|
);
|
|
447
463
|
|
|
448
464
|
const avatar = getAvatarContainer('ST');
|
|
465
|
+
// Avatar uses Tailwind v4 size-* utility instead of h-* w-*
|
|
449
466
|
expect(avatar).toHaveClass(
|
|
450
|
-
'
|
|
451
|
-
'flex',
|
|
452
|
-
'h-10',
|
|
453
|
-
'w-10',
|
|
454
|
-
'shrink-0',
|
|
467
|
+
'size-10',
|
|
455
468
|
'overflow-hidden',
|
|
456
469
|
'rounded-full'
|
|
457
470
|
);
|
|
@@ -459,19 +472,15 @@ describe('Avatar Component', () => {
|
|
|
459
472
|
|
|
460
473
|
it('applies correct fallback styling', () => {
|
|
461
474
|
renderWithProviders(
|
|
462
|
-
<Avatar
|
|
463
|
-
<AvatarFallback>FS</AvatarFallback>
|
|
464
|
-
</Avatar>
|
|
475
|
+
<Avatar fallback="FS" />
|
|
465
476
|
);
|
|
466
477
|
|
|
467
478
|
const fallback = screen.getByText('FS');
|
|
468
479
|
expect(fallback).toHaveClass(
|
|
469
|
-
'
|
|
470
|
-
'
|
|
471
|
-
'
|
|
472
|
-
'
|
|
473
|
-
'justify-center',
|
|
474
|
-
'rounded-full',
|
|
480
|
+
'size-full',
|
|
481
|
+
'grid',
|
|
482
|
+
'place-items-center',
|
|
483
|
+
'text-center',
|
|
475
484
|
'text-sec-50',
|
|
476
485
|
'bg-sec-500'
|
|
477
486
|
);
|
|
@@ -479,13 +488,30 @@ describe('Avatar Component', () => {
|
|
|
479
488
|
|
|
480
489
|
it('handles custom size overrides', () => {
|
|
481
490
|
renderWithProviders(
|
|
482
|
-
<Avatar className="
|
|
483
|
-
<AvatarFallback>CS</AvatarFallback>
|
|
484
|
-
</Avatar>
|
|
491
|
+
<Avatar className="size-20" fallback="CS" />
|
|
485
492
|
);
|
|
486
493
|
|
|
487
494
|
const avatar = getAvatarContainer('CS');
|
|
488
|
-
expect(avatar).toHaveClass('
|
|
495
|
+
expect(avatar).toHaveClass('size-20');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('applies size variants correctly', () => {
|
|
499
|
+
const sizes: Array<'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'> = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
500
|
+
|
|
501
|
+
sizes.forEach(size => {
|
|
502
|
+
const { unmount } = renderWithProviders(
|
|
503
|
+
<Avatar size={size} fallback={size.toUpperCase()} />
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const avatar = getAvatarContainer(size.toUpperCase());
|
|
507
|
+
if (size === 'md') {
|
|
508
|
+
expect(avatar).toHaveClass('size-10');
|
|
509
|
+
} else {
|
|
510
|
+
expect(avatar).toHaveClass(sizeClasses[size].split(' ')[0]);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
unmount();
|
|
514
|
+
});
|
|
489
515
|
});
|
|
490
516
|
});
|
|
491
|
-
});
|
|
517
|
+
});
|