@jmruthers/pace-core 0.5.68 → 0.5.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/dist/{DataTable-4IUY7BXB.js → DataTable-OSELOGMA.js} +6 -6
  2. package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-DLpF5bbs.d.ts} +78 -2
  3. package/dist/{chunk-OPCWH3A4.js → chunk-4YMVZ76F.js} +7 -6
  4. package/dist/chunk-4YMVZ76F.js.map +1 -0
  5. package/dist/{chunk-NN45OBIS.js → chunk-5G7JA3L5.js} +3 -5
  6. package/dist/{chunk-NN45OBIS.js.map → chunk-5G7JA3L5.js.map} +1 -1
  7. package/dist/{chunk-U6GPOF6J.js → chunk-5NV76BYF.js} +666 -110
  8. package/dist/chunk-5NV76BYF.js.map +1 -0
  9. package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
  10. package/dist/chunk-6RBH67W7.js.map +1 -0
  11. package/dist/{chunk-ZPG4XPV5.js → chunk-BHBMXMLT.js} +5 -7
  12. package/dist/chunk-BHBMXMLT.js.map +1 -0
  13. package/dist/{chunk-ZMS23NS5.js → chunk-FOT3WUV6.js} +3 -5
  14. package/dist/{chunk-ZMS23NS5.js.map → chunk-FOT3WUV6.js.map} +1 -1
  15. package/dist/{chunk-MOJXHWDE.js → chunk-GCUIIBLB.js} +382 -5
  16. package/dist/chunk-GCUIIBLB.js.map +1 -0
  17. package/dist/{chunk-PXWEDX7Y.js → chunk-KWQH4VO3.js} +3 -3
  18. package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
  19. package/dist/chunk-O3NWNXDY.js.map +1 -0
  20. package/dist/{chunk-KRCRNXPD.js → chunk-OTJUAYBG.js} +81 -18
  21. package/dist/chunk-OTJUAYBG.js.map +1 -0
  22. package/dist/chunk-SMJZMKYN.js +141 -0
  23. package/dist/chunk-SMJZMKYN.js.map +1 -0
  24. package/dist/{chunk-UYA6U6H7.js → chunk-V2TE7LOF.js} +4 -4
  25. package/dist/{chunk-L3RV2ALE.js → chunk-VKOCWWVY.js} +6 -1
  26. package/dist/{chunk-L3RV2ALE.js.map → chunk-VKOCWWVY.js.map} +1 -1
  27. package/dist/components.d.ts +4 -79
  28. package/dist/components.js +23 -581
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.d.ts +1 -1
  31. package/dist/hooks.js +9 -6
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.js +32 -19
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.js +6 -7
  37. package/dist/rbac/index.js +6 -6
  38. package/dist/styles/index.js +2 -2
  39. package/dist/theming/runtime.d.ts +4 -3
  40. package/dist/theming/runtime.js +3 -1
  41. package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
  42. package/dist/utils.d.ts +4 -1
  43. package/dist/utils.js +3 -3
  44. package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
  45. package/docs/README.md +22 -10
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +1 -1
  60. package/docs/api/enums/FileCategory.md +129 -0
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/ButtonProps.md +1 -1
  63. package/docs/api/interfaces/CardProps.md +1 -1
  64. package/docs/api/interfaces/ColorPalette.md +1 -1
  65. package/docs/api/interfaces/ColorShade.md +1 -1
  66. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  67. package/docs/api/interfaces/DataTableAction.md +1 -1
  68. package/docs/api/interfaces/DataTableColumn.md +1 -1
  69. package/docs/api/interfaces/DataTableProps.md +1 -1
  70. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  71. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  72. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  73. package/docs/api/interfaces/EventContextType.md +7 -7
  74. package/docs/api/interfaces/EventLogoProps.md +1 -1
  75. package/docs/api/interfaces/EventProviderProps.md +2 -2
  76. package/docs/api/interfaces/FileDisplayProps.md +107 -0
  77. package/docs/api/interfaces/FileMetadata.md +129 -0
  78. package/docs/api/interfaces/FileReference.md +118 -0
  79. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  80. package/docs/api/interfaces/FileUploadOptions.md +85 -0
  81. package/docs/api/interfaces/FileUploadProps.md +1 -1
  82. package/docs/api/interfaces/FooterProps.md +1 -1
  83. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  84. package/docs/api/interfaces/InputProps.md +1 -1
  85. package/docs/api/interfaces/LabelProps.md +1 -1
  86. package/docs/api/interfaces/LoginFormProps.md +1 -1
  87. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  88. package/docs/api/interfaces/NavigationContextType.md +1 -1
  89. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  90. package/docs/api/interfaces/NavigationItem.md +1 -1
  91. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  92. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  93. package/docs/api/interfaces/Organisation.md +1 -1
  94. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  95. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  96. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  97. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  98. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  99. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  100. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  101. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  102. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  103. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  104. package/docs/api/interfaces/PaletteData.md +1 -1
  105. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  106. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  107. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  108. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  109. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  110. package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
  111. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  112. package/docs/api/interfaces/RBACConfig.md +1 -1
  113. package/docs/api/interfaces/RBACContextType.md +1 -1
  114. package/docs/api/interfaces/RBACLogger.md +1 -1
  115. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  116. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  117. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  118. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  119. package/docs/api/interfaces/RouteConfig.md +1 -1
  120. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  121. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  122. package/docs/api/interfaces/StorageConfig.md +1 -1
  123. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  124. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  125. package/docs/api/interfaces/StorageListOptions.md +1 -1
  126. package/docs/api/interfaces/StorageListResult.md +1 -1
  127. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  128. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  129. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  130. package/docs/api/interfaces/StyleImport.md +1 -1
  131. package/docs/api/interfaces/SwitchProps.md +1 -1
  132. package/docs/api/interfaces/ToastActionElement.md +1 -1
  133. package/docs/api/interfaces/ToastProps.md +1 -1
  134. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  135. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  136. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  137. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  139. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  140. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  141. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  142. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  143. package/docs/api/interfaces/UserEventAccess.md +1 -1
  144. package/docs/api/interfaces/UserMenuProps.md +1 -1
  145. package/docs/api/interfaces/UserProfile.md +1 -1
  146. package/docs/api/modules.md +228 -23
  147. package/docs/architecture/services.md +374 -0
  148. package/docs/best-practices/README.md +1 -1
  149. package/docs/best-practices/testing.md +1 -1
  150. package/docs/breaking-changes.md +182 -0
  151. package/docs/common-patterns.md +445 -0
  152. package/docs/core-concepts/authentication.md +26 -11
  153. package/docs/core-concepts/events.md +2 -0
  154. package/docs/core-concepts/organisations.md +2 -0
  155. package/docs/core-concepts/permissions.md +2 -0
  156. package/docs/{INDEX.md → documentation-index.md} +26 -38
  157. package/docs/faq.md +286 -0
  158. package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
  159. package/docs/getting-started/installation-guide.md +284 -0
  160. package/docs/getting-started/quick-start.md +8 -1
  161. package/docs/implementation-guides/app-layout.md +3 -1
  162. package/docs/implementation-guides/data-tables.md +2 -0
  163. package/docs/implementation-guides/dynamic-colors.md +47 -2
  164. package/docs/implementation-guides/event-theming-summary.md +220 -0
  165. package/docs/implementation-guides/forms.md +9 -7
  166. package/docs/implementation-guides/navigation.md +2 -0
  167. package/docs/migration/service-architecture.md +351 -0
  168. package/docs/rbac/README-rbac-rls-integration.md +2 -2
  169. package/docs/rbac/README.md +1 -1
  170. package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
  171. package/docs/rbac/quick-start.md +2 -0
  172. package/docs/rbac/rbac-rls-integration.md +2 -2
  173. package/docs/style-guide.md +136 -1
  174. package/docs/testing/README.md +1 -1
  175. package/docs/troubleshooting/authentication-issues.md +334 -0
  176. package/docs/troubleshooting/common-issues.md +2 -0
  177. package/docs/troubleshooting/styling-issues.md +199 -144
  178. package/docs/usage.md +23 -2
  179. package/package.json +3 -2
  180. package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
  181. package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
  182. package/src/__tests__/fixtures/test-data.ts +90 -0
  183. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
  184. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
  185. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
  186. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
  187. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
  188. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
  189. package/src/__tests__/helpers/component-test-utils.tsx +14 -4
  190. package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
  191. package/src/__tests__/helpers/test-providers.tsx +329 -0
  192. package/src/__tests__/helpers/test-utils.tsx +91 -45
  193. package/src/__tests__/helpers/timer-utils.ts +71 -0
  194. package/src/__tests__/hooks/usePermissions.test.ts +1 -5
  195. package/src/__tests__/integration/UserProfile.test.tsx +1 -5
  196. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
  197. package/src/__tests__/setup.ts +34 -28
  198. package/src/components/Alert/Alert.test.tsx +1 -5
  199. package/src/components/Avatar/Avatar.test.tsx +1 -5
  200. package/src/components/Button/Button.test.tsx +4 -20
  201. package/src/components/Card/Card.test.tsx +1 -5
  202. package/src/components/Checkbox/Checkbox.test.tsx +1 -5
  203. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
  204. package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
  205. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
  206. package/src/components/DataTable/__tests__/styles.test.ts +382 -0
  207. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
  208. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
  209. package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
  210. package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
  211. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
  212. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
  213. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
  214. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
  215. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
  216. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
  217. package/src/components/Footer/Footer.test.tsx +1 -5
  218. package/src/components/Form/Form.test.tsx +22 -35
  219. package/src/components/Header/Header.test.tsx +1 -9
  220. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
  221. package/src/components/Input/Input.test.tsx +2 -10
  222. package/src/components/LoginForm/LoginForm.test.tsx +1 -5
  223. package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
  224. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
  225. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
  226. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -4
  227. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
  228. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
  229. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
  230. package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
  231. package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
  232. package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
  233. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
  234. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
  235. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
  236. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
  237. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
  238. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
  239. package/src/components/Select/Select.bug-test.tsx +69 -0
  240. package/src/components/Select/Select.refactored.tsx +497 -0
  241. package/src/components/Select/Select.test.tsx +42 -49
  242. package/src/components/Select/Select.tsx +5 -2
  243. package/src/components/Select/hooks.ts +254 -0
  244. package/src/components/Switch/Switch.test.tsx +1 -5
  245. package/src/components/Table/__tests__/Table.test.tsx +775 -0
  246. package/src/components/Toast/Toast.test.tsx +15 -8
  247. package/src/components/Tooltip/Tooltip.test.tsx +1 -5
  248. package/src/components/UserMenu/UserMenu.test.tsx +3 -15
  249. package/src/components/__tests__/FileDisplay.test.tsx +575 -0
  250. package/src/components/__tests__/FileUpload.test.tsx +446 -0
  251. package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
  252. package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
  253. package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
  254. package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
  255. package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
  256. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
  257. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
  258. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
  259. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
  260. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  261. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
  262. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
  263. package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
  264. package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
  265. package/src/hooks/index.ts +3 -0
  266. package/src/hooks/public/usePublicEvent.ts +30 -9
  267. package/src/hooks/public/usePublicRouteParams.ts +13 -3
  268. package/src/hooks/services/useAuth.ts +50 -0
  269. package/src/hooks/services/useAuthService.ts +30 -0
  270. package/src/hooks/services/useCurrentEvent.ts +36 -0
  271. package/src/hooks/services/useCurrentOrganisation.ts +52 -0
  272. package/src/hooks/services/useEventService.ts +30 -0
  273. package/src/hooks/services/useInactivityService.ts +30 -0
  274. package/src/hooks/services/useOrganisationService.ts +30 -0
  275. package/src/hooks/services/usePermissions.ts +70 -0
  276. package/src/hooks/services/useRBACService.ts +30 -0
  277. package/src/hooks/useCounter.test.ts +1 -5
  278. package/src/hooks/useEventTheme.ts +86 -0
  279. package/src/hooks/useOrganisationPermissions.test.ts +2 -5
  280. package/src/hooks/useOrganisationSecurity.test.ts +1 -5
  281. package/src/hooks/usePermissionCache.test.ts +1 -5
  282. package/src/hooks/usePermissionCheck.ts +150 -0
  283. package/src/hooks/useSecureDataAccess.test.ts +1 -5
  284. package/src/index.ts +7 -0
  285. package/src/providers/EventProvider.tsx +58 -2
  286. package/src/providers/OrganisationProvider.test.tsx +1 -5
  287. package/src/providers/OrganisationProvider.tsx +56 -4
  288. package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
  289. package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
  290. package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
  291. package/src/providers/__tests__/EventProvider.test.tsx +211 -110
  292. package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
  293. package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
  294. package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
  295. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
  296. package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
  297. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
  298. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
  299. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
  300. package/src/providers/services/AuthServiceProvider.tsx +65 -0
  301. package/src/providers/services/EventServiceProvider.tsx +83 -0
  302. package/src/providers/services/InactivityServiceProvider.tsx +83 -0
  303. package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
  304. package/src/providers/services/RBACServiceProvider.tsx +79 -0
  305. package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
  306. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
  307. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
  308. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
  309. package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
  310. package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
  311. package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
  312. package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
  313. package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
  314. package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
  315. package/src/rbac/api.test.ts +1 -9
  316. package/src/rbac/cache.test.ts +10 -8
  317. package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
  318. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
  319. package/src/rbac/components/NavigationProvider.test.tsx +1 -5
  320. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
  321. package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
  322. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
  323. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
  324. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
  325. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
  326. package/src/rbac/config.test.ts +1 -5
  327. package/src/rbac/hooks/useCan.test.ts +262 -9
  328. package/src/rbac/hooks/usePermissions.test.ts +246 -6
  329. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
  330. package/src/rbac/hooks/useRBAC.test.ts +472 -198
  331. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
  332. package/src/services/AuthService.ts +416 -0
  333. package/src/services/EventService.ts +366 -0
  334. package/src/services/InactivityService.ts +388 -0
  335. package/src/services/OrganisationService.ts +592 -0
  336. package/src/services/RBACService.ts +522 -0
  337. package/src/services/__tests__/AuthService.test.ts +356 -0
  338. package/src/services/__tests__/BaseService.test.ts +314 -0
  339. package/src/services/__tests__/EventService.test.ts +489 -0
  340. package/src/services/__tests__/InactivityService.test.ts +403 -0
  341. package/src/services/__tests__/OrganisationService.test.ts +660 -0
  342. package/src/services/__tests__/RBACService.test.ts +492 -0
  343. package/src/services/base/BaseService.ts +87 -0
  344. package/src/services/interfaces/IAuthService.ts +39 -0
  345. package/src/services/interfaces/IEventService.ts +30 -0
  346. package/src/services/interfaces/IInactivityService.ts +31 -0
  347. package/src/services/interfaces/IOrganisationService.ts +41 -0
  348. package/src/services/interfaces/IRBACService.ts +62 -0
  349. package/src/theming/__tests__/runtime.test.ts +560 -0
  350. package/src/theming/runtime.ts +71 -28
  351. package/src/types/__tests__/file-reference.test.ts +447 -0
  352. package/src/types/__tests__/organisation.test.ts +1133 -0
  353. package/src/types/__tests__/theme.test.ts +830 -0
  354. package/src/types/__tests__/type-validation.test.ts +527 -0
  355. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
  356. package/src/utils/__tests__/debugLogger.test.ts +417 -0
  357. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
  358. package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
  359. package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
  360. package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
  361. package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
  362. package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
  363. package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
  364. package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
  365. package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
  366. package/src/utils/appIdResolver.test.ts +6 -10
  367. package/src/utils/appNameResolver.simple.test.ts +142 -0
  368. package/src/utils/appNameResolver.test.ts +31 -458
  369. package/src/utils/appNameResolver.test.ts.backup +494 -0
  370. package/src/utils/debugLogger.ts +26 -5
  371. package/src/utils/formatDate.test.ts +1 -5
  372. package/src/utils/organisationContext.test.ts +1 -5
  373. package/src/utils/performanceBudgets.ts +3 -4
  374. package/src/utils/secureDataAccess.test.ts +1 -5
  375. package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
  376. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
  377. package/dist/chunk-D7ARGIA3.js.map +0 -1
  378. package/dist/chunk-IPCH4YPT.js +0 -315
  379. package/dist/chunk-IPCH4YPT.js.map +0 -1
  380. package/dist/chunk-KRCRNXPD.js.map +0 -1
  381. package/dist/chunk-MOJXHWDE.js.map +0 -1
  382. package/dist/chunk-N2EUGZRW.js +0 -98
  383. package/dist/chunk-N2EUGZRW.js.map +0 -1
  384. package/dist/chunk-OPCWH3A4.js.map +0 -1
  385. package/dist/chunk-U6GPOF6J.js.map +0 -1
  386. package/dist/chunk-ZPG4XPV5.js.map +0 -1
  387. package/dist/chunk-ZPK5656W.js.map +0 -1
  388. package/docs/getting-started/installation.md +0 -269
  389. package/src/__tests__/REBUILD_PLAN.md +0 -223
  390. package/src/styles/base.css +0 -208
  391. package/src/styles/semantic.css +0 -24
  392. /package/dist/{DataTable-4IUY7BXB.js.map → DataTable-OSELOGMA.js.map} +0 -0
  393. /package/dist/{chunk-PXWEDX7Y.js.map → chunk-KWQH4VO3.js.map} +0 -0
  394. /package/dist/{chunk-UYA6U6H7.js.map → chunk-V2TE7LOF.js.map} +0 -0
@@ -22,12 +22,11 @@ import { useToast, reset } from '../../hooks/useToast';
22
22
  import { renderWithProviders } from '../../__tests__/helpers/test-utils';
23
23
 
24
24
  describe('Toast Component System', () => {
25
+ // Clear toast state before each test
25
26
  beforeEach(() => {
26
- vi.clearAllMocks();
27
- reset(); // Reset toast state between tests
27
+ reset();
28
28
  });
29
-
30
- // ToastProvider component tests
29
+ // ToastProvider component tests
31
30
  describe('ToastProvider Component', () => {
32
31
  it('renders provider wrapper', () => {
33
32
  renderWithProviders(
@@ -184,7 +183,10 @@ describe('Toast Component System', () => {
184
183
  const button = screen.getByText('Show Toast');
185
184
  await user.click(button);
186
185
 
187
- expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
186
+ // Wait for state to update through the listener system
187
+ await waitFor(() => {
188
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
189
+ });
188
190
  });
189
191
 
190
192
  it('dismisses toast when dismiss is called', async () => {
@@ -207,13 +209,18 @@ describe('Toast Component System', () => {
207
209
  const showButton = screen.getByText('Show Toast');
208
210
  await user.click(showButton);
209
211
 
210
- expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
212
+ // Wait for toast to be added
213
+ await waitFor(() => {
214
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
215
+ });
211
216
 
212
217
  const dismissButton = screen.getByText('Dismiss');
213
218
  await user.click(dismissButton);
214
219
 
215
- // Toast should be marked as closed but still in array
216
- expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
220
+ // Toast should be marked as closed but still in array (waiting to be removed)
221
+ await waitFor(() => {
222
+ expect(screen.getByTestId('toast-count')).toHaveTextContent('1');
223
+ });
217
224
  });
218
225
  });
219
226
 
@@ -26,11 +26,7 @@ const MockButton = ({ children, ...props }: { children: React.ReactNode; [key: s
26
26
  const MockIcon = () => <span data-testid="mock-icon">ℹ️</span>;
27
27
 
28
28
  describe('Tooltip Component Suite', () => {
29
- beforeEach(() => {
30
- vi.clearAllMocks();
31
- });
32
-
33
- describe('Tooltip Component', () => {
29
+ describe('Tooltip Component', () => {
34
30
  describe('Rendering', () => {
35
31
  it('renders with basic content', () => {
36
32
  renderWithProviders(
@@ -164,11 +164,7 @@ const createMockUserWithEmailOnly = (): User => ({
164
164
  } as User);
165
165
 
166
166
  describe('UserMenu Component', () => {
167
- beforeEach(() => {
168
- vi.clearAllMocks();
169
- });
170
-
171
- describe('Rendering', () => {
167
+ describe('Rendering', () => {
172
168
  it('renders with user information', () => {
173
169
  const user = createMockUser();
174
170
  renderWithProviders(<UserMenu user={user} />);
@@ -564,11 +560,7 @@ describe('UserMenu Component', () => {
564
560
  });
565
561
 
566
562
  describe('UserMenuLoading Component', () => {
567
- beforeEach(() => {
568
- vi.clearAllMocks();
569
- });
570
-
571
- describe('Rendering', () => {
563
+ describe('Rendering', () => {
572
564
  it('renders loading state', () => {
573
565
  renderWithProviders(<UserMenuLoading />);
574
566
 
@@ -626,11 +618,7 @@ describe('UserMenuLoading Component', () => {
626
618
  });
627
619
 
628
620
  describe('UserMenu Component Integration', () => {
629
- beforeEach(() => {
630
- vi.clearAllMocks();
631
- });
632
-
633
- describe('Complex Scenarios', () => {
621
+ describe('Complex Scenarios', () => {
634
622
  it('handles rapid menu interactions', async () => {
635
623
  const user = createMockUser();
636
624
  const handleSignOut = vi.fn();
@@ -0,0 +1,575 @@
1
+ /**
2
+ * @file FileDisplay Component Tests
3
+ * @description Comprehensive test suite for FileDisplay component
4
+ * @package @jmruthers/pace-core
5
+ * @module Components/FileDisplay
6
+ * @since 1.0.0
7
+ */
8
+
9
+ import React from 'react';
10
+ import { screen, waitFor, fireEvent } from '@testing-library/react';
11
+ import userEvent from '@testing-library/user-event';
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
+ import { FileDisplay } from '../FileDisplay';
14
+ import { renderWithProviders, createMockSupabaseClient } from '../../__tests__/helpers/test-utils';
15
+ import { FileCategory } from '../../types/file-reference';
16
+
17
+ // Mock the useFileReferenceForRecord hook
18
+ const mockUseFileReferenceForRecord = vi.fn();
19
+ vi.mock('../../hooks/useFileReference', () => ({
20
+ useFileReferenceForRecord: () => mockUseFileReferenceForRecord(),
21
+ }));
22
+
23
+ // Mock window.confirm
24
+ const mockConfirm = vi.fn();
25
+ Object.defineProperty(window, 'confirm', {
26
+ value: mockConfirm,
27
+ writable: true,
28
+ });
29
+
30
+ describe('FileDisplay Component', () => {
31
+ const defaultProps = {
32
+ supabase: createMockSupabaseClient(),
33
+ table_name: 'test_table',
34
+ record_id: 'test_record_123',
35
+ organisation_id: 'test_org_123',
36
+ };
37
+
38
+ const mockFileReference = {
39
+ id: 'file_123',
40
+ table_name: 'test_table',
41
+ record_id: 'test_record_123',
42
+ file_path: 'uploads/test-file.jpg',
43
+ file_metadata: {
44
+ fileName: 'test-file.jpg',
45
+ fileType: 'image/jpeg',
46
+ fileSize: 1024000,
47
+ category: FileCategory.IMAGES,
48
+ },
49
+ organisation_id: 'test_org_123',
50
+ app_id: 'test_app',
51
+ is_public: true,
52
+ created_at: '2023-01-01T00:00:00Z',
53
+ updated_at: '2023-01-01T00:00:00Z',
54
+ };
55
+
56
+ const mockFileReferences = [
57
+ mockFileReference,
58
+ {
59
+ ...mockFileReference,
60
+ id: 'file_456',
61
+ file_metadata: {
62
+ ...mockFileReference.file_metadata,
63
+ fileName: 'document.pdf',
64
+ fileType: 'application/pdf',
65
+ fileSize: 2048000,
66
+ },
67
+ },
68
+ ];
69
+
70
+ beforeEach(() => {
71
+ vi.clearAllMocks();
72
+ mockConfirm.mockReturnValue(true);
73
+
74
+ // Default mock implementation
75
+ mockUseFileReferenceForRecord.mockReturnValue({
76
+ isLoading: false,
77
+ error: null,
78
+ fileUrl: 'https://example.com/test-file.jpg',
79
+ fileReference: null,
80
+ fileReferences: [],
81
+ fileCount: 0,
82
+ loadFileReference: vi.fn(),
83
+ loadFileUrl: vi.fn(),
84
+ loadFileReferences: vi.fn(),
85
+ loadFileCount: vi.fn(),
86
+ deleteFile: vi.fn().mockResolvedValue(true),
87
+ clearError: vi.fn(),
88
+ });
89
+ });
90
+
91
+ describe('Rendering', () => {
92
+ it('renders with basic props', () => {
93
+ renderWithProviders(<FileDisplay {...defaultProps} />);
94
+ expect(screen.getByText('No files found')).toBeInTheDocument();
95
+ });
96
+
97
+ it('renders with custom className', () => {
98
+ const { container } = renderWithProviders(
99
+ <FileDisplay {...defaultProps} className="custom-class" />
100
+ );
101
+ expect(container.firstChild).toHaveClass('custom-class');
102
+ });
103
+
104
+ it('renders with children', () => {
105
+ renderWithProviders(
106
+ <FileDisplay {...defaultProps}>
107
+ <div data-testid="custom-child">Custom content</div>
108
+ </FileDisplay>
109
+ );
110
+ expect(screen.getByTestId('custom-child')).toBeInTheDocument();
111
+ });
112
+ });
113
+
114
+ describe('Loading State', () => {
115
+ it('shows loading spinner when isLoading is true', () => {
116
+ mockUseFileReferenceForRecord.mockReturnValue({
117
+ ...mockUseFileReferenceForRecord(),
118
+ isLoading: true,
119
+ });
120
+
121
+ renderWithProviders(<FileDisplay {...defaultProps} />);
122
+
123
+ const spinner = document.querySelector('.animate-spin');
124
+ expect(spinner).toBeInTheDocument();
125
+ expect(spinner).toHaveClass('animate-spin');
126
+ });
127
+ });
128
+
129
+ describe('Error State', () => {
130
+ it('displays error message when error occurs', () => {
131
+ const errorMessage = 'Failed to load files';
132
+ mockUseFileReferenceForRecord.mockReturnValue({
133
+ ...mockUseFileReferenceForRecord(),
134
+ error: errorMessage,
135
+ });
136
+
137
+ renderWithProviders(<FileDisplay {...defaultProps} />);
138
+
139
+ expect(screen.getByText(`Error loading file: ${errorMessage}`)).toBeInTheDocument();
140
+ });
141
+
142
+ it('calls clearError when try again button is clicked', async () => {
143
+ const clearError = vi.fn();
144
+ mockUseFileReferenceForRecord.mockReturnValue({
145
+ ...mockUseFileReferenceForRecord(),
146
+ error: 'Test error',
147
+ clearError,
148
+ });
149
+
150
+ const user = userEvent.setup();
151
+ renderWithProviders(<FileDisplay {...defaultProps} />);
152
+
153
+ await user.click(screen.getByText('Try again'));
154
+ expect(clearError).toHaveBeenCalledTimes(1);
155
+ });
156
+ });
157
+
158
+ describe('Empty State', () => {
159
+ it('shows no files found when fileCount is 0', () => {
160
+ mockUseFileReferenceForRecord.mockReturnValue({
161
+ ...mockUseFileReferenceForRecord(),
162
+ fileCount: 0,
163
+ });
164
+
165
+ renderWithProviders(<FileDisplay {...defaultProps} />);
166
+ expect(screen.getByText('No files found')).toBeInTheDocument();
167
+ });
168
+ });
169
+
170
+ describe('Single File Display', () => {
171
+ beforeEach(() => {
172
+ mockUseFileReferenceForRecord.mockReturnValue({
173
+ ...mockUseFileReferenceForRecord(),
174
+ fileReference: mockFileReference,
175
+ fileCount: 1,
176
+ });
177
+ });
178
+
179
+ it('displays image file with image element', () => {
180
+ renderWithProviders(
181
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
182
+ );
183
+
184
+ const image = screen.getByRole('img');
185
+ expect(image).toBeInTheDocument();
186
+ expect(image).toHaveAttribute('src', 'https://example.com/test-file.jpg');
187
+ expect(image).toHaveAttribute('alt', 'test-file.jpg');
188
+ });
189
+
190
+ it('displays non-image file with file icon and metadata', () => {
191
+ const pdfFileReference = {
192
+ ...mockFileReference,
193
+ file_metadata: {
194
+ ...mockFileReference.file_metadata,
195
+ fileName: 'document.pdf',
196
+ fileType: 'application/pdf',
197
+ fileSize: 2048000,
198
+ },
199
+ };
200
+
201
+ mockUseFileReferenceForRecord.mockReturnValue({
202
+ ...mockUseFileReferenceForRecord(),
203
+ fileReference: pdfFileReference,
204
+ fileCount: 1,
205
+ });
206
+
207
+ renderWithProviders(
208
+ <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
209
+ );
210
+
211
+ expect(screen.getByText('document.pdf')).toBeInTheDocument();
212
+ expect(screen.getAllByText((content, element) =>
213
+ element?.textContent?.includes('1.95 MB') && element?.textContent?.includes('application/pdf') || false
214
+ )[0]).toBeInTheDocument();
215
+ expect(screen.getByText('📄')).toBeInTheDocument(); // PDF icon
216
+ });
217
+
218
+ it('handles image load error by showing file icon', async () => {
219
+ const user = userEvent.setup();
220
+ renderWithProviders(
221
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
222
+ );
223
+
224
+ const image = screen.getByRole('img');
225
+ await user.click(image); // Trigger error by clicking
226
+
227
+ // Simulate image error
228
+ fireEvent.error(image);
229
+
230
+ await waitFor(() => {
231
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
232
+ // Check if the file is still displayed (either as img or text)
233
+ const imgElement = screen.queryByRole('img', { name: 'test-file.jpg' });
234
+ const textElement = screen.queryByText('test-file.jpg');
235
+ expect(imgElement || textElement).toBeInTheDocument();
236
+ });
237
+ });
238
+
239
+ it('shows delete button when showDelete is true', () => {
240
+ renderWithProviders(
241
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
242
+ );
243
+
244
+ const deleteButton = screen.getByTitle('Delete file');
245
+ expect(deleteButton).toBeInTheDocument();
246
+ });
247
+
248
+ it('does not show delete button when showDelete is false', () => {
249
+ renderWithProviders(
250
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete={false} />
251
+ );
252
+
253
+ expect(screen.queryByTitle('Delete file')).not.toBeInTheDocument();
254
+ });
255
+
256
+ it('calls deleteFile when delete button is clicked and confirmed', async () => {
257
+ const deleteFile = vi.fn().mockResolvedValue(true);
258
+ mockUseFileReferenceForRecord.mockReturnValue({
259
+ ...mockUseFileReferenceForRecord(),
260
+ fileReference: mockFileReference,
261
+ fileCount: 1,
262
+ deleteFile,
263
+ });
264
+
265
+ const user = userEvent.setup();
266
+ renderWithProviders(
267
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
268
+ );
269
+
270
+ await user.click(screen.getByTitle('Delete file'));
271
+
272
+ expect(mockConfirm).toHaveBeenCalledWith('Are you sure you want to delete this file?');
273
+ expect(deleteFile).toHaveBeenCalledWith(true);
274
+ });
275
+
276
+ it('does not delete file when user cancels confirmation', async () => {
277
+ mockConfirm.mockReturnValue(false);
278
+ const deleteFile = vi.fn();
279
+ mockUseFileReferenceForRecord.mockReturnValue({
280
+ ...mockUseFileReferenceForRecord(),
281
+ fileReference: mockFileReference,
282
+ fileCount: 1,
283
+ deleteFile,
284
+ });
285
+
286
+ const user = userEvent.setup();
287
+ renderWithProviders(
288
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
289
+ );
290
+
291
+ await user.click(screen.getByTitle('Delete file'));
292
+
293
+ expect(mockConfirm).toHaveBeenCalledWith('Are you sure you want to delete this file?');
294
+ expect(deleteFile).not.toHaveBeenCalled();
295
+ });
296
+ });
297
+
298
+ describe('Multiple Files Display', () => {
299
+ beforeEach(() => {
300
+ mockUseFileReferenceForRecord.mockReturnValue({
301
+ ...mockUseFileReferenceForRecord(),
302
+ fileReferences: mockFileReferences,
303
+ fileCount: 2,
304
+ });
305
+ });
306
+
307
+ it('displays multiple files in a list', () => {
308
+ renderWithProviders(<FileDisplay {...defaultProps} />);
309
+
310
+ expect(screen.getByText('test-file.jpg')).toBeInTheDocument();
311
+ expect(screen.getByText('document.pdf')).toBeInTheDocument();
312
+ expect(screen.getAllByText((content, element) =>
313
+ element?.textContent?.includes('1000 KB') && element?.textContent?.includes('image/jpeg') || false
314
+ )[0]).toBeInTheDocument();
315
+ expect(screen.getAllByText((content, element) =>
316
+ element?.textContent?.includes('1.95 MB') && element?.textContent?.includes('application/pdf') || false
317
+ )[0]).toBeInTheDocument();
318
+ });
319
+
320
+ it('shows delete buttons for each file when showDelete is true', () => {
321
+ renderWithProviders(<FileDisplay {...defaultProps} showDelete />);
322
+
323
+ const deleteButtons = screen.getAllByTitle('Delete file');
324
+ expect(deleteButtons).toHaveLength(2);
325
+ });
326
+
327
+ it('calls deleteFile for specific file when delete button is clicked', async () => {
328
+ const deleteFile = vi.fn().mockResolvedValue(true);
329
+ mockUseFileReferenceForRecord.mockReturnValue({
330
+ ...mockUseFileReferenceForRecord(),
331
+ fileReferences: mockFileReferences,
332
+ fileCount: 2,
333
+ deleteFile,
334
+ });
335
+
336
+ const user = userEvent.setup();
337
+ renderWithProviders(<FileDisplay {...defaultProps} showDelete />);
338
+
339
+ const deleteButtons = screen.getAllByTitle('Delete file');
340
+ await user.click(deleteButtons[0]);
341
+
342
+ expect(deleteFile).toHaveBeenCalledWith(true);
343
+ });
344
+ });
345
+
346
+ describe('File Icons', () => {
347
+ it('shows correct icon for different file types', () => {
348
+ const testCases = [
349
+ { fileType: 'image/jpeg', expectedIcon: '🖼️' },
350
+ { fileType: 'video/mp4', expectedIcon: '🎥' },
351
+ { fileType: 'audio/mp3', expectedIcon: '🎵' },
352
+ { fileType: 'application/pdf', expectedIcon: '📄' },
353
+ { fileType: 'application/msword', expectedIcon: '📝' },
354
+ { fileType: 'application/vnd.ms-excel', expectedIcon: '📊' },
355
+ { fileType: 'application/vnd.ms-powerpoint', expectedIcon: '📊' },
356
+ { fileType: 'text/plain', expectedIcon: '📁' },
357
+ ];
358
+
359
+ testCases.forEach(({ fileType, expectedIcon }) => {
360
+ const fileRef = {
361
+ ...mockFileReference,
362
+ file_metadata: {
363
+ ...mockFileReference.file_metadata,
364
+ fileType,
365
+ },
366
+ };
367
+
368
+ mockUseFileReferenceForRecord.mockReturnValue({
369
+ ...mockUseFileReferenceForRecord(),
370
+ fileReference: fileRef,
371
+ fileCount: 1,
372
+ });
373
+
374
+ const { unmount } = renderWithProviders(
375
+ <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
376
+ );
377
+
378
+ // For image files, check if it's an img element or icon
379
+ if (fileType.startsWith('image/')) {
380
+ const imgElement = screen.queryByRole('img');
381
+ if (imgElement) {
382
+ expect(imgElement).toBeInTheDocument();
383
+ } else {
384
+ expect(screen.getByText(expectedIcon)).toBeInTheDocument();
385
+ }
386
+ } else {
387
+ expect(screen.getByText(expectedIcon)).toBeInTheDocument();
388
+ }
389
+ unmount();
390
+ });
391
+ });
392
+ });
393
+
394
+ describe('File Size Formatting', () => {
395
+ it('formats file sizes correctly', () => {
396
+ const testCases = [
397
+ { size: 0, expected: '0 Bytes' },
398
+ { size: 1024, expected: '1 KB' },
399
+ { size: 1024 * 1024, expected: '1 MB' },
400
+ { size: 1024 * 1024 * 1024, expected: '1 GB' },
401
+ { size: 1536, expected: '1.5 KB' },
402
+ ];
403
+
404
+ testCases.forEach(({ size, expected }) => {
405
+ const fileRef = {
406
+ ...mockFileReference,
407
+ file_metadata: {
408
+ ...mockFileReference.file_metadata,
409
+ fileSize: size,
410
+ },
411
+ };
412
+
413
+ mockUseFileReferenceForRecord.mockReturnValue({
414
+ ...mockUseFileReferenceForRecord(),
415
+ fileReference: fileRef,
416
+ fileCount: 1,
417
+ });
418
+
419
+ const { unmount } = renderWithProviders(
420
+ <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
421
+ );
422
+
423
+ // For image files, check if it's an img element or text
424
+ if (fileRef.file_metadata.fileType.startsWith('image/')) {
425
+ const imgElement = screen.queryByRole('img');
426
+ if (imgElement) {
427
+ expect(imgElement).toBeInTheDocument();
428
+ } else {
429
+ expect(screen.getByText(expected)).toBeInTheDocument();
430
+ }
431
+ } else {
432
+ expect(screen.getByText(expected)).toBeInTheDocument();
433
+ }
434
+ unmount();
435
+ });
436
+ });
437
+ });
438
+
439
+ describe('Accessibility', () => {
440
+ it('has proper alt text for images', () => {
441
+ mockUseFileReferenceForRecord.mockReturnValue({
442
+ ...mockUseFileReferenceForRecord(),
443
+ fileReference: mockFileReference,
444
+ fileCount: 1,
445
+ });
446
+
447
+ renderWithProviders(
448
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
449
+ );
450
+
451
+ const image = screen.getByRole('img');
452
+ expect(image).toHaveAttribute('alt', 'test-file.jpg');
453
+ });
454
+
455
+ it('has proper title attributes for delete buttons', () => {
456
+ mockUseFileReferenceForRecord.mockReturnValue({
457
+ ...mockUseFileReferenceForRecord(),
458
+ fileReference: mockFileReference,
459
+ fileCount: 1,
460
+ });
461
+
462
+ renderWithProviders(
463
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
464
+ );
465
+
466
+ const deleteButton = screen.getByTitle('Delete file');
467
+ expect(deleteButton).toBeInTheDocument();
468
+ });
469
+ });
470
+
471
+ describe('Integration', () => {
472
+ it('loads file data on mount', () => {
473
+ const loadFileCount = vi.fn();
474
+ const loadFileReference = vi.fn();
475
+ const loadFileReferences = vi.fn();
476
+
477
+ mockUseFileReferenceForRecord.mockReturnValue({
478
+ ...mockUseFileReferenceForRecord(),
479
+ loadFileCount,
480
+ loadFileReference,
481
+ loadFileReferences,
482
+ });
483
+
484
+ renderWithProviders(
485
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
486
+ );
487
+
488
+ expect(loadFileCount).toHaveBeenCalledTimes(1);
489
+ expect(loadFileReference).toHaveBeenCalledTimes(1);
490
+ expect(loadFileReferences).not.toHaveBeenCalled();
491
+ });
492
+
493
+ it('loads file references when no category is specified', () => {
494
+ const loadFileCount = vi.fn();
495
+ const loadFileReference = vi.fn();
496
+ const loadFileReferences = vi.fn();
497
+
498
+ mockUseFileReferenceForRecord.mockReturnValue({
499
+ ...mockUseFileReferenceForRecord(),
500
+ loadFileCount,
501
+ loadFileReference,
502
+ loadFileReferences,
503
+ });
504
+
505
+ renderWithProviders(<FileDisplay {...defaultProps} />);
506
+
507
+ expect(loadFileCount).toHaveBeenCalledTimes(1);
508
+ expect(loadFileReferences).toHaveBeenCalledTimes(1);
509
+ expect(loadFileReference).not.toHaveBeenCalled();
510
+ });
511
+
512
+ it('loads file URL when file reference is available', () => {
513
+ const loadFileUrl = vi.fn();
514
+ mockUseFileReferenceForRecord.mockReturnValue({
515
+ ...mockUseFileReferenceForRecord(),
516
+ fileReference: mockFileReference,
517
+ fileCount: 1,
518
+ loadFileUrl,
519
+ });
520
+
521
+ renderWithProviders(
522
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
523
+ );
524
+
525
+ expect(loadFileUrl).toHaveBeenCalledTimes(1);
526
+ });
527
+ });
528
+
529
+ describe('Error Handling', () => {
530
+ it('handles missing file metadata gracefully', () => {
531
+ const fileRefWithoutMetadata = {
532
+ ...mockFileReference,
533
+ file_metadata: {
534
+ fileName: undefined,
535
+ fileType: undefined,
536
+ fileSize: undefined,
537
+ category: FileCategory.IMAGES,
538
+ },
539
+ };
540
+
541
+ mockUseFileReferenceForRecord.mockReturnValue({
542
+ ...mockUseFileReferenceForRecord(),
543
+ fileReference: fileRefWithoutMetadata,
544
+ fileCount: 1,
545
+ });
546
+
547
+ renderWithProviders(
548
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
549
+ );
550
+
551
+ expect(screen.getByText('Unknown file')).toBeInTheDocument();
552
+ });
553
+
554
+ it('handles delete failure gracefully', async () => {
555
+ const deleteFile = vi.fn().mockResolvedValue(false);
556
+ mockUseFileReferenceForRecord.mockReturnValue({
557
+ ...mockUseFileReferenceForRecord(),
558
+ fileReference: mockFileReference,
559
+ fileCount: 1,
560
+ deleteFile,
561
+ });
562
+
563
+ const user = userEvent.setup();
564
+ renderWithProviders(
565
+ <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
566
+ );
567
+
568
+ await user.click(screen.getByTitle('Delete file'));
569
+
570
+ expect(deleteFile).toHaveBeenCalledWith(true);
571
+ // File should still be displayed since deletion failed
572
+ expect(screen.getByRole('img', { name: 'test-file.jpg' })).toBeInTheDocument();
573
+ });
574
+ });
575
+ });