@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.
Files changed (424) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -5
  38. package/dist/components.js +19 -23
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -12
  46. package/dist/index.js +79 -73
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +128 -0
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +155 -135
  214. package/docs/api-reference/components.md +72 -29
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -4
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +252 -226
  243. package/src/components/Avatar/Avatar.tsx +179 -53
  244. package/src/components/Avatar/index.ts +1 -1
  245. package/src/components/Button/Button.test.tsx +2 -1
  246. package/src/components/Button/Button.tsx +3 -3
  247. package/src/components/Calendar/Calendar.test.tsx +53 -37
  248. package/src/components/Calendar/Calendar.tsx +409 -82
  249. package/src/components/Card/Card.test.tsx +7 -4
  250. package/src/components/Card/Card.tsx +3 -6
  251. package/src/components/Checkbox/Checkbox.tsx +2 -2
  252. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  253. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  254. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  255. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  256. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  257. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  258. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  259. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  260. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  261. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  262. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  263. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  264. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  265. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  266. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  267. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  268. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  269. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  270. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  271. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  272. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  273. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  274. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  275. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  276. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  277. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  278. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  279. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  280. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  281. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  282. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  284. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  285. package/src/components/Dialog/Dialog.tsx +2 -2
  286. package/src/components/EventSelector/EventSelector.tsx +7 -7
  287. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  288. package/src/components/FileUpload/FileUpload.tsx +7 -4
  289. package/src/components/Header/Header.test.tsx +28 -0
  290. package/src/components/Header/Header.tsx +22 -9
  291. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  292. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  293. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  294. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  295. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  296. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  299. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  300. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  301. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  302. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  303. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  304. package/src/components/Progress/Progress.test.tsx +18 -19
  305. package/src/components/Progress/Progress.tsx +31 -32
  306. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  307. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  308. package/src/components/Select/Select.tsx +5 -5
  309. package/src/components/Switch/Switch.test.tsx +2 -1
  310. package/src/components/Switch/Switch.tsx +1 -1
  311. package/src/components/Toast/Toast.tsx +1 -1
  312. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  313. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  314. package/src/components/UserMenu/UserMenu.tsx +10 -8
  315. package/src/components/index.ts +2 -1
  316. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  317. package/src/eslint-rules/pace-core-compliance.js +0 -2
  318. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  319. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  320. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  321. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  322. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  323. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  324. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  325. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  326. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  327. package/src/hooks/index.ts +1 -1
  328. package/src/hooks/public/usePublicEvent.ts +2 -2
  329. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  330. package/src/hooks/useAppConfig.ts +24 -5
  331. package/src/hooks/useFileDisplay.ts +297 -34
  332. package/src/hooks/useFileReference.ts +56 -11
  333. package/src/hooks/useFileUrl.ts +1 -1
  334. package/src/hooks/useInactivityTracker.ts +16 -7
  335. package/src/hooks/usePermissionCache.test.ts +85 -8
  336. package/src/hooks/useQueryCache.ts +21 -0
  337. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  338. package/src/hooks/useSecureDataAccess.ts +80 -37
  339. package/src/index.ts +2 -1
  340. package/src/providers/services/EventServiceProvider.tsx +37 -17
  341. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  342. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  343. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  344. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  345. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  346. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  347. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  348. package/src/rbac/api.ts +240 -36
  349. package/src/rbac/cache-invalidation.ts +21 -7
  350. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  351. package/src/rbac/components/NavigationGuard.tsx +23 -63
  352. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  353. package/src/rbac/components/NavigationProvider.tsx +13 -11
  354. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  355. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  356. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  357. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  358. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  359. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  360. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  361. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  362. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  363. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  364. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  365. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  366. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  367. package/src/rbac/engine.ts +4 -2
  368. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  369. package/src/rbac/hooks/index.ts +3 -0
  370. package/src/rbac/hooks/useCan.test.ts +101 -53
  371. package/src/rbac/hooks/usePermissions.ts +108 -41
  372. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  373. package/src/rbac/hooks/useRBAC.ts +83 -40
  374. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  375. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  376. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  377. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  378. package/src/rbac/request-deduplication.ts +1 -1
  379. package/src/rbac/secureClient.ts +72 -12
  380. package/src/rbac/security.ts +29 -23
  381. package/src/rbac/types.ts +10 -0
  382. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  383. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  384. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  385. package/src/rbac/utils/contextValidator.ts +288 -0
  386. package/src/rbac/utils/eventContext.ts +48 -2
  387. package/src/services/EventService.ts +165 -21
  388. package/src/services/OrganisationService.ts +37 -2
  389. package/src/services/__tests__/EventService.test.ts +26 -21
  390. package/src/types/file-reference.ts +13 -10
  391. package/src/utils/app/appNameResolver.test.ts +346 -73
  392. package/src/utils/context/superAdminOverride.ts +58 -0
  393. package/src/utils/file-reference/index.ts +61 -33
  394. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  395. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  396. package/src/utils/storage/helpers.test.ts +1 -1
  397. package/src/utils/storage/helpers.ts +38 -19
  398. package/src/utils/storage/types.ts +15 -8
  399. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  400. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  401. package/src/vite-env.d.ts +2 -2
  402. package/dist/chunk-3GOZZZYH.js.map +0 -1
  403. package/dist/chunk-DDM4CCYT.js.map +0 -1
  404. package/dist/chunk-E7UAOUMY.js +0 -75
  405. package/dist/chunk-E7UAOUMY.js.map +0 -1
  406. package/dist/chunk-EFCLXK7F.js.map +0 -1
  407. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  408. package/dist/chunk-HEHYGYOX.js.map +0 -1
  409. package/dist/chunk-IM4QE42D.js.map +0 -1
  410. package/dist/chunk-IPCH26AG.js.map +0 -1
  411. package/dist/chunk-SAUPYVLF.js.map +0 -1
  412. package/dist/chunk-THRPYOFK.js.map +0 -1
  413. package/dist/chunk-UNOTYLQF.js.map +0 -1
  414. package/dist/chunk-VGZZXKBR.js.map +0 -1
  415. package/dist/chunk-YHCN776L.js.map +0 -1
  416. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  417. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  418. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  419. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  420. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  421. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  422. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  423. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  424. /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, AvatarImage, and AvatarFallback components
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, AvatarImage, AvatarFallback } from './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
- // Helper function to get the avatar container element
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
- return screen.getByText(fallbackText).closest('span')?.parentElement;
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
- describe('Rendering', () => {
19
- it('renders with default props', () => {
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
- expect(avatar).toHaveClass('relative', 'flex', 'h-10', 'w-10', 'shrink-0', 'overflow-hidden', 'rounded-full');
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="h-16 w-16">
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('h-16', 'w-16');
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<HTMLSpanElement>();
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
- expect(ref.current).toBeInstanceOf(HTMLSpanElement);
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('AvatarImage Component', () => {
78
- it('renders with src and alt attributes when image loads', async () => {
79
- // Mock successful image load
80
- const mockImage = {
81
- onload: null as (() => void) | null,
82
- onerror: null as (() => void) | null,
83
- src: '',
84
- alt: '',
85
- addEventListener: vi.fn(),
86
- removeEventListener: vi.fn(),
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
- // Initially shows fallback
107
- expect(screen.getByText('UI')).toBeInTheDocument();
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
- const image = screen.queryByAltText('User profile');
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('shows fallback when image fails to load', () => {
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
- // Should show fallback when image fails
128
- expect(screen.getByText('FL')).toBeInTheDocument();
129
- expect(screen.queryByAltText('User')).not.toBeInTheDocument();
175
+ const image = screen.getByAltText('JD');
176
+ expect(image).toBeInTheDocument();
130
177
  });
131
178
 
132
- it('handles missing src gracefully', () => {
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
- // Should show fallback when image src is missing
141
- expect(screen.getByText('MS')).toBeInTheDocument();
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
- it('forwards ref correctly', () => {
145
- const ref = React.createRef<HTMLImageElement>();
146
-
147
- renderWithProviders(
148
- <Avatar>
149
- <AvatarImage ref={ref} src="/user.jpg" alt="User" />
150
- <AvatarFallback>RF</AvatarFallback>
151
- </Avatar>
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
- // Ref might be null if image doesn't load immediately, which is expected behavior
155
- // The important thing is that the component renders without errors
156
- expect(screen.getByText('RF')).toBeInTheDocument();
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('accepts HTML attributes', () => {
210
+ it('requires all file props for file reference approach', () => {
160
211
  renderWithProviders(
161
- <Avatar>
162
- <AvatarImage
163
- src="/user.jpg"
164
- alt="User"
165
- data-testid="avatar-image"
166
- loading="lazy"
167
- />
168
- <AvatarFallback>AT</AvatarFallback>
169
- </Avatar>
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('AvatarFallback Component', () => {
181
- it('renders with text content', () => {
226
+ describe('File ID Approach', () => {
227
+ it('shows fallback when fileId provided but no supabase', () => {
182
228
  renderWithProviders(
183
- <Avatar>
184
- <AvatarFallback>AB</AvatarFallback>
185
- </Avatar>
229
+ <Avatar
230
+ fileId="file-123"
231
+ organisation_id="org-123"
232
+ fallback="FI"
233
+ />
186
234
  );
187
235
 
188
- const fallback = screen.getByText('AB');
189
- expect(fallback).toBeInTheDocument();
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
- it('renders with custom className', () => {
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('CF');
201
- expect(fallback).toHaveClass('custom-fallback-class');
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('forwards ref correctly', () => {
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
- expect(ref.current).toBeInstanceOf(HTMLSpanElement);
280
+ const fallback = screen.getByText('AB');
281
+ expect(fallback).toHaveAttribute('aria-label', 'User avatar');
234
282
  });
235
283
 
236
- it('accepts HTML attributes', () => {
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.getByTestId('fallback');
246
- expect(fallback).toHaveAttribute('aria-label', 'User initials');
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
- <AvatarFallback>AB</AvatarFallback>
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
- // For empty content, we can check that the avatar container exists
348
- // by looking for the specific class structure
349
- const avatar = document.querySelector('.relative.flex.h-10.w-10');
350
- expect(avatar).toBeInTheDocument();
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.getByText('UP')).toBeInTheDocument();
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
- 'relative',
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
- 'flex',
470
- 'h-full',
471
- 'w-full',
472
- 'items-center',
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="h-20 w-20">
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('h-20', 'w-20');
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
+ });