@jmruthers/pace-core 0.5.67 → 0.5.69

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 (384) hide show
  1. package/README.md +26 -0
  2. package/dist/{DataTable-MFUXNGPR.js → DataTable-MPBSXUC6.js} +5 -6
  3. package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-BOdyU3u-.d.ts} +1 -1
  4. package/dist/{UnifiedAuthProvider-CQNiemcB.d.ts → UnifiedAuthProvider-D02AMXgO.d.ts} +3 -3
  5. package/dist/{chunk-CKNY7HYS.js → chunk-2ARQW6VX.js} +3 -3
  6. package/dist/{chunk-T2MQY57J.js → chunk-6JILXFEA.js} +335 -5
  7. package/dist/chunk-6JILXFEA.js.map +1 -0
  8. package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
  9. package/dist/chunk-6RBH67W7.js.map +1 -0
  10. package/dist/{chunk-C7GUF747.js → chunk-FJTAWPAQ.js} +3 -5
  11. package/dist/{chunk-C7GUF747.js.map → chunk-FJTAWPAQ.js.map} +1 -1
  12. package/dist/{chunk-4HQ5BOVZ.js → chunk-NO5QHMDX.js} +7 -6
  13. package/dist/chunk-NO5QHMDX.js.map +1 -0
  14. package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
  15. package/dist/chunk-O3NWNXDY.js.map +1 -0
  16. package/dist/{chunk-BTCA3ENN.js → chunk-Q2UP3ZWQ.js} +4 -4
  17. package/dist/{chunk-QVEOQVD4.js → chunk-RVYGJPOD.js} +173 -20
  18. package/dist/chunk-RVYGJPOD.js.map +1 -0
  19. package/dist/{chunk-FVDOEGGG.js → chunk-UCMHBF7Y.js} +3 -5
  20. package/dist/{chunk-FVDOEGGG.js.map → chunk-UCMHBF7Y.js.map} +1 -1
  21. package/dist/{chunk-T6HVDA24.js → chunk-V3QO3LL7.js} +5 -7
  22. package/dist/chunk-V3QO3LL7.js.map +1 -0
  23. package/dist/{chunk-ZB6AEA7I.js → chunk-ZXJGZLLO.js} +17 -17
  24. package/dist/{chunk-ZB6AEA7I.js.map → chunk-ZXJGZLLO.js.map} +1 -1
  25. package/dist/components.d.ts +2 -2
  26. package/dist/components.js +8 -9
  27. package/dist/components.js.map +1 -1
  28. package/dist/hooks.d.ts +1 -1
  29. package/dist/hooks.js +9 -6
  30. package/dist/hooks.js.map +1 -1
  31. package/dist/index.d.ts +4 -4
  32. package/dist/index.js +16 -16
  33. package/dist/index.js.map +1 -1
  34. package/dist/providers.d.ts +1 -1
  35. package/dist/providers.js +5 -7
  36. package/dist/rbac/index.js +5 -6
  37. package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
  38. package/dist/utils.d.ts +4 -1
  39. package/dist/utils.js +3 -3
  40. package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
  41. package/docs/README.md +22 -10
  42. package/docs/api/README.md +26 -0
  43. package/docs/api/classes/ColumnFactory.md +1 -1
  44. package/docs/api/classes/ErrorBoundary.md +1 -1
  45. package/docs/api/classes/InvalidScopeError.md +1 -1
  46. package/docs/api/classes/MissingUserContextError.md +1 -1
  47. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  48. package/docs/api/classes/PermissionDeniedError.md +1 -1
  49. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  50. package/docs/api/classes/RBACAuditManager.md +1 -1
  51. package/docs/api/classes/RBACCache.md +1 -1
  52. package/docs/api/classes/RBACEngine.md +1 -1
  53. package/docs/api/classes/RBACError.md +1 -1
  54. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  55. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  56. package/docs/api/classes/StorageUtils.md +1 -1
  57. package/docs/api/interfaces/AggregateConfig.md +1 -1
  58. package/docs/api/interfaces/ButtonProps.md +1 -1
  59. package/docs/api/interfaces/CardProps.md +1 -1
  60. package/docs/api/interfaces/ColorPalette.md +1 -1
  61. package/docs/api/interfaces/ColorShade.md +1 -1
  62. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  63. package/docs/api/interfaces/DataTableAction.md +1 -1
  64. package/docs/api/interfaces/DataTableColumn.md +1 -1
  65. package/docs/api/interfaces/DataTableProps.md +1 -1
  66. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  67. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  68. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  69. package/docs/api/interfaces/EventContextType.md +1 -1
  70. package/docs/api/interfaces/EventLogoProps.md +1 -1
  71. package/docs/api/interfaces/EventProviderProps.md +1 -1
  72. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  73. package/docs/api/interfaces/FileUploadProps.md +1 -1
  74. package/docs/api/interfaces/FooterProps.md +1 -1
  75. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  76. package/docs/api/interfaces/InputProps.md +1 -1
  77. package/docs/api/interfaces/LabelProps.md +1 -1
  78. package/docs/api/interfaces/LoginFormProps.md +1 -1
  79. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  80. package/docs/api/interfaces/NavigationContextType.md +1 -1
  81. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  82. package/docs/api/interfaces/NavigationItem.md +1 -1
  83. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  84. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  85. package/docs/api/interfaces/Organisation.md +1 -1
  86. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  87. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  88. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  89. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  90. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  91. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  92. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  93. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  94. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  95. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  96. package/docs/api/interfaces/PaletteData.md +1 -1
  97. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  98. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  99. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  100. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  101. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  102. package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
  103. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  104. package/docs/api/interfaces/RBACConfig.md +1 -1
  105. package/docs/api/interfaces/RBACContextType.md +1 -1
  106. package/docs/api/interfaces/RBACLogger.md +1 -1
  107. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  108. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  109. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  110. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  111. package/docs/api/interfaces/RouteConfig.md +1 -1
  112. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  113. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  114. package/docs/api/interfaces/StorageConfig.md +1 -1
  115. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  116. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  117. package/docs/api/interfaces/StorageListOptions.md +1 -1
  118. package/docs/api/interfaces/StorageListResult.md +1 -1
  119. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  120. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  121. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  122. package/docs/api/interfaces/StyleImport.md +1 -1
  123. package/docs/api/interfaces/SwitchProps.md +1 -1
  124. package/docs/api/interfaces/ToastActionElement.md +1 -1
  125. package/docs/api/interfaces/ToastProps.md +1 -1
  126. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  127. package/docs/api/interfaces/UnifiedAuthProviderProps.md +4 -4
  128. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  129. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  130. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  131. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  132. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  133. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  134. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  135. package/docs/api/interfaces/UserEventAccess.md +1 -1
  136. package/docs/api/interfaces/UserMenuProps.md +1 -1
  137. package/docs/api/interfaces/UserProfile.md +1 -1
  138. package/docs/api/modules.md +39 -14
  139. package/docs/api-reference/providers.md +16 -7
  140. package/docs/architecture/services.md +374 -0
  141. package/docs/best-practices/README.md +1 -1
  142. package/docs/best-practices/testing.md +1 -1
  143. package/docs/breaking-changes.md +182 -0
  144. package/docs/common-patterns.md +445 -0
  145. package/docs/core-concepts/authentication.md +26 -11
  146. package/docs/core-concepts/events.md +2 -0
  147. package/docs/core-concepts/organisations.md +2 -0
  148. package/docs/core-concepts/permissions.md +2 -0
  149. package/docs/{INDEX.md → documentation-index.md} +26 -38
  150. package/docs/faq.md +286 -0
  151. package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
  152. package/docs/getting-started/installation-guide.md +284 -0
  153. package/docs/getting-started/quick-start.md +8 -1
  154. package/docs/implementation-guides/app-layout.md +3 -1
  155. package/docs/implementation-guides/data-tables.md +2 -0
  156. package/docs/implementation-guides/dynamic-colors.md +47 -2
  157. package/docs/implementation-guides/event-theming-summary.md +220 -0
  158. package/docs/implementation-guides/forms.md +9 -7
  159. package/docs/implementation-guides/navigation.md +2 -0
  160. package/docs/migration/service-architecture.md +351 -0
  161. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +226 -0
  162. package/docs/rbac/README-rbac-rls-integration.md +2 -2
  163. package/docs/rbac/README.md +1 -1
  164. package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
  165. package/docs/rbac/quick-start.md +2 -0
  166. package/docs/rbac/rbac-rls-integration.md +2 -2
  167. package/docs/security/README.md +5 -1
  168. package/docs/style-guide.md +136 -1
  169. package/docs/testing/README.md +1 -1
  170. package/docs/troubleshooting/authentication-issues.md +334 -0
  171. package/docs/troubleshooting/common-issues.md +2 -0
  172. package/docs/troubleshooting/styling-issues.md +199 -144
  173. package/docs/usage.md +23 -2
  174. package/package.json +1 -1
  175. package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
  176. package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
  177. package/src/__tests__/fixtures/test-data.ts +90 -0
  178. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
  179. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
  180. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
  181. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
  182. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
  183. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
  184. package/src/__tests__/helpers/component-test-utils.tsx +14 -4
  185. package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
  186. package/src/__tests__/helpers/test-providers.tsx +329 -0
  187. package/src/__tests__/helpers/test-utils.tsx +91 -45
  188. package/src/__tests__/helpers/timer-utils.ts +71 -0
  189. package/src/__tests__/hooks/usePermissions.test.ts +1 -5
  190. package/src/__tests__/integration/UserProfile.test.tsx +1 -5
  191. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
  192. package/src/__tests__/setup.ts +34 -28
  193. package/src/components/Alert/Alert.test.tsx +1 -5
  194. package/src/components/Avatar/Avatar.test.tsx +1 -5
  195. package/src/components/Button/Button.test.tsx +4 -20
  196. package/src/components/Card/Card.test.tsx +1 -5
  197. package/src/components/Checkbox/Checkbox.test.tsx +1 -5
  198. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
  199. package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
  200. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
  201. package/src/components/DataTable/__tests__/styles.test.ts +382 -0
  202. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
  203. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
  204. package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
  205. package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
  206. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
  207. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
  208. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
  209. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
  210. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
  212. package/src/components/Footer/Footer.test.tsx +1 -5
  213. package/src/components/Form/Form.test.tsx +22 -35
  214. package/src/components/Header/Header.test.tsx +1 -9
  215. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
  216. package/src/components/Input/Input.test.tsx +2 -10
  217. package/src/components/LoginForm/LoginForm.test.tsx +1 -5
  218. package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
  219. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
  220. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
  221. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -5
  222. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
  223. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
  224. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
  225. package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
  226. package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
  227. package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
  228. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
  229. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
  230. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
  231. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
  232. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
  233. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
  234. package/src/components/Select/Select.bug-test.tsx +69 -0
  235. package/src/components/Select/Select.refactored.tsx +497 -0
  236. package/src/components/Select/Select.test.tsx +42 -49
  237. package/src/components/Select/Select.tsx +5 -2
  238. package/src/components/Select/hooks.ts +254 -0
  239. package/src/components/Switch/Switch.test.tsx +1 -5
  240. package/src/components/Table/__tests__/Table.test.tsx +775 -0
  241. package/src/components/Toast/Toast.test.tsx +15 -8
  242. package/src/components/Tooltip/Tooltip.test.tsx +1 -5
  243. package/src/components/UserMenu/UserMenu.test.tsx +3 -15
  244. package/src/components/__tests__/FileDisplay.test.tsx +575 -0
  245. package/src/components/__tests__/FileUpload.test.tsx +446 -0
  246. package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
  247. package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
  248. package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
  249. package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
  250. package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
  251. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
  252. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
  253. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
  254. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
  255. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  256. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
  257. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
  258. package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
  259. package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
  260. package/src/hooks/index.ts +3 -0
  261. package/src/hooks/public/usePublicEvent.ts +181 -13
  262. package/src/hooks/public/usePublicRouteParams.ts +13 -3
  263. package/src/hooks/services/useAuth.ts +50 -0
  264. package/src/hooks/services/useAuthService.ts +30 -0
  265. package/src/hooks/services/useCurrentEvent.ts +36 -0
  266. package/src/hooks/services/useCurrentOrganisation.ts +52 -0
  267. package/src/hooks/services/useEventService.ts +30 -0
  268. package/src/hooks/services/useInactivityService.ts +30 -0
  269. package/src/hooks/services/useOrganisationService.ts +30 -0
  270. package/src/hooks/services/usePermissions.ts +70 -0
  271. package/src/hooks/services/useRBACService.ts +30 -0
  272. package/src/hooks/useCounter.test.ts +1 -5
  273. package/src/hooks/useEventTheme.ts +86 -0
  274. package/src/hooks/useOrganisationPermissions.test.ts +2 -5
  275. package/src/hooks/useOrganisationSecurity.test.ts +1 -5
  276. package/src/hooks/usePermissionCache.test.ts +1 -5
  277. package/src/hooks/usePermissionCheck.ts +150 -0
  278. package/src/hooks/useSecureDataAccess.test.ts +1 -5
  279. package/src/index.ts +1 -0
  280. package/src/providers/OrganisationProvider.test.tsx +1 -5
  281. package/src/providers/OrganisationProvider.tsx +56 -4
  282. package/src/providers/UnifiedAuthProvider.test.simple.tsx +42 -6
  283. package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
  284. package/src/providers/UnifiedAuthProvider.tsx +4 -4
  285. package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
  286. package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
  287. package/src/providers/__tests__/EventProvider.test.tsx +211 -110
  288. package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
  289. package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
  290. package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
  291. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
  292. package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
  293. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
  294. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
  295. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
  296. package/src/providers/services/AuthServiceProvider.tsx +65 -0
  297. package/src/providers/services/EventServiceProvider.tsx +83 -0
  298. package/src/providers/services/InactivityServiceProvider.tsx +83 -0
  299. package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
  300. package/src/providers/services/RBACServiceProvider.tsx +79 -0
  301. package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
  302. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
  303. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
  304. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
  305. package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
  306. package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
  307. package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
  308. package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
  309. package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
  310. package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
  311. package/src/rbac/api.test.ts +1 -9
  312. package/src/rbac/cache.test.ts +10 -8
  313. package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
  314. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
  315. package/src/rbac/components/NavigationProvider.test.tsx +1 -5
  316. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
  317. package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
  318. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
  319. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
  320. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
  321. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
  322. package/src/rbac/config.test.ts +1 -5
  323. package/src/rbac/hooks/useCan.test.ts +262 -9
  324. package/src/rbac/hooks/usePermissions.test.ts +246 -6
  325. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
  326. package/src/rbac/hooks/useRBAC.test.ts +472 -198
  327. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
  328. package/src/services/AuthService.ts +416 -0
  329. package/src/services/EventService.ts +366 -0
  330. package/src/services/InactivityService.ts +388 -0
  331. package/src/services/OrganisationService.ts +592 -0
  332. package/src/services/RBACService.ts +522 -0
  333. package/src/services/__tests__/AuthService.test.ts +356 -0
  334. package/src/services/__tests__/BaseService.test.ts +314 -0
  335. package/src/services/__tests__/EventService.test.ts +489 -0
  336. package/src/services/__tests__/InactivityService.test.ts +403 -0
  337. package/src/services/__tests__/OrganisationService.test.ts +660 -0
  338. package/src/services/__tests__/RBACService.test.ts +492 -0
  339. package/src/services/base/BaseService.ts +87 -0
  340. package/src/services/interfaces/IAuthService.ts +39 -0
  341. package/src/services/interfaces/IEventService.ts +30 -0
  342. package/src/services/interfaces/IInactivityService.ts +31 -0
  343. package/src/services/interfaces/IOrganisationService.ts +41 -0
  344. package/src/services/interfaces/IRBACService.ts +62 -0
  345. package/src/theming/__tests__/runtime.test.ts +540 -0
  346. package/src/types/__tests__/file-reference.test.ts +447 -0
  347. package/src/types/__tests__/organisation.test.ts +1133 -0
  348. package/src/types/__tests__/theme.test.ts +830 -0
  349. package/src/types/__tests__/type-validation.test.ts +527 -0
  350. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
  351. package/src/utils/__tests__/debugLogger.test.ts +417 -0
  352. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
  353. package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
  354. package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
  355. package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
  356. package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
  357. package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
  358. package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
  359. package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
  360. package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
  361. package/src/utils/appIdResolver.test.ts +6 -10
  362. package/src/utils/appNameResolver.simple.test.ts +142 -0
  363. package/src/utils/appNameResolver.test.ts +31 -458
  364. package/src/utils/appNameResolver.test.ts.backup +494 -0
  365. package/src/utils/debugLogger.ts +26 -5
  366. package/src/utils/formatDate.test.ts +1 -5
  367. package/src/utils/organisationContext.test.ts +1 -5
  368. package/src/utils/performanceBudgets.ts +3 -4
  369. package/src/utils/secureDataAccess.test.ts +1 -5
  370. package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
  371. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
  372. package/dist/chunk-4HQ5BOVZ.js.map +0 -1
  373. package/dist/chunk-D7ARGIA3.js.map +0 -1
  374. package/dist/chunk-QVEOQVD4.js.map +0 -1
  375. package/dist/chunk-T2MQY57J.js.map +0 -1
  376. package/dist/chunk-T6HVDA24.js.map +0 -1
  377. package/dist/chunk-VTJ5HCZB.js +0 -315
  378. package/dist/chunk-VTJ5HCZB.js.map +0 -1
  379. package/dist/chunk-ZPK5656W.js.map +0 -1
  380. package/docs/getting-started/installation.md +0 -269
  381. package/src/__tests__/REBUILD_PLAN.md +0 -223
  382. /package/dist/{DataTable-MFUXNGPR.js.map → DataTable-MPBSXUC6.js.map} +0 -0
  383. /package/dist/{chunk-CKNY7HYS.js.map → chunk-2ARQW6VX.js.map} +0 -0
  384. /package/dist/{chunk-BTCA3ENN.js.map → chunk-Q2UP3ZWQ.js.map} +0 -0
@@ -0,0 +1,824 @@
1
+ /**
2
+ * @file EventProvider Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Providers/__tests__
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive test suite for EventProvider component covering all critical functionality.
8
+ * Follows testing guidelines with proper structure, naming, and best practices.
9
+ */
10
+
11
+ import React from 'react';
12
+ import { render, screen, waitFor, act } from '@testing-library/react';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ import { EventProvider, useEvents } from '../EventProvider';
16
+ import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
17
+
18
+ // Mock the debug logger
19
+ vi.mock('../../utils/debugLogger', () => ({
20
+ DebugLogger: {
21
+ log: vi.fn(),
22
+ },
23
+ }));
24
+
25
+ // Mock the organisation context utility
26
+ vi.mock('../../utils/organisationContext', () => ({
27
+ setOrganisationContext: vi.fn().mockResolvedValue(undefined),
28
+ }));
29
+
30
+ // Mock UnifiedAuthProvider
31
+ const mockUnifiedAuthState = {
32
+ user: null as any,
33
+ session: null as any,
34
+ supabase: null as any,
35
+ appName: 'test-app',
36
+ setSelectedEventId: vi.fn(),
37
+ };
38
+
39
+ vi.mock('../UnifiedAuthProvider', () => ({
40
+ useUnifiedAuth: () => ({
41
+ user: mockUnifiedAuthState.user,
42
+ session: mockUnifiedAuthState.session,
43
+ supabase: mockUnifiedAuthState.supabase,
44
+ appName: mockUnifiedAuthState.appName,
45
+ setSelectedEventId: mockUnifiedAuthState.setSelectedEventId,
46
+ }),
47
+ }));
48
+
49
+ // Mock OrganisationProvider
50
+ const mockOrganisationState = {
51
+ selectedOrganisation: null as any,
52
+ ensureOrganisationContext: vi.fn(),
53
+ };
54
+
55
+ vi.mock('../OrganisationProvider', () => ({
56
+ useOrganisations: () => ({
57
+ selectedOrganisation: mockOrganisationState.selectedOrganisation,
58
+ ensureOrganisationContext: mockOrganisationState.ensureOrganisationContext,
59
+ }),
60
+ }));
61
+
62
+ // Test component that uses the hook
63
+ const TestComponent = () => {
64
+ const { events, isLoading, error, selectedEvent, setSelectedEvent, refreshEvents } = useEvents();
65
+
66
+ return (
67
+ <div data-testid="test-component">
68
+ <div data-testid="events-count">{events.length}</div>
69
+ <div data-testid="is-loading">{isLoading ? 'true' : 'false'}</div>
70
+ <div data-testid="error">{error?.message || 'no-error'}</div>
71
+ <div data-testid="selected-event">{selectedEvent?.event_name || 'no-event'}</div>
72
+ <div data-testid="selected-event-id">{selectedEvent?.event_id || 'no-id'}</div>
73
+ <button onClick={() => setSelectedEvent(events[0] || null)}>
74
+ Select First Event
75
+ </button>
76
+ <button onClick={() => setSelectedEvent(null)}>
77
+ Clear Event
78
+ </button>
79
+ <button onClick={() => refreshEvents()}>
80
+ Refresh Events
81
+ </button>
82
+ </div>
83
+ );
84
+ };
85
+
86
+ // Wrapper component
87
+ const TestWrapper = ({
88
+ children,
89
+ supabaseClient,
90
+ user = null,
91
+ session = null,
92
+ appName = 'test-app',
93
+ selectedOrganisation = null
94
+ }: {
95
+ children: React.ReactNode;
96
+ supabaseClient?: any;
97
+ user?: any;
98
+ session?: any;
99
+ appName?: string;
100
+ selectedOrganisation?: any;
101
+ }) => {
102
+ // Update mock state
103
+ mockUnifiedAuthState.user = user;
104
+ mockUnifiedAuthState.session = session;
105
+ mockUnifiedAuthState.supabase = supabaseClient;
106
+ mockUnifiedAuthState.appName = appName;
107
+ mockOrganisationState.selectedOrganisation = selectedOrganisation;
108
+
109
+ return (
110
+ <EventProvider supabaseClient={supabaseClient}>
111
+ {children}
112
+ </EventProvider>
113
+ );
114
+ };
115
+
116
+ describe('[component] EventProvider', () => {
117
+ let mockSupabaseClient: any;
118
+
119
+ const mockEvents = [
120
+ {
121
+ event_id: 'event-1',
122
+ event_name: 'Test Event 1',
123
+ event_date: '2024-12-31T18:00:00Z',
124
+ event_venue: 'Test Venue 1',
125
+ event_participants: 100,
126
+ event_colours: { primary: '#ff0000', secondary: '#00ff00' },
127
+ organisation_id: 'org-1',
128
+ is_visible: true,
129
+ },
130
+ {
131
+ event_id: 'event-2',
132
+ event_name: 'Test Event 2',
133
+ event_date: '2025-01-15T19:00:00Z',
134
+ event_venue: 'Test Venue 2',
135
+ event_participants: 150,
136
+ event_colours: { primary: '#0000ff', secondary: '#ffff00' },
137
+ organisation_id: 'org-1',
138
+ is_visible: true,
139
+ },
140
+ {
141
+ event_id: 'event-3',
142
+ event_name: 'Past Event',
143
+ event_date: '2023-01-01T12:00:00Z',
144
+ event_venue: 'Old Venue',
145
+ event_participants: 50,
146
+ event_colours: { primary: '#888888', secondary: '#cccccc' },
147
+ organisation_id: 'org-1',
148
+ is_visible: true,
149
+ }
150
+ ];
151
+
152
+ const mockOrganisation = {
153
+ id: 'org-1',
154
+ name: 'test-org',
155
+ display_name: 'Test Organisation',
156
+ subscription_tier: 'standard',
157
+ settings: {},
158
+ is_active: true,
159
+ parent_id: null,
160
+ created_at: '2023-01-01T00:00:00Z',
161
+ updated_at: '2023-01-01T00:00:00Z'
162
+ };
163
+
164
+ beforeEach(() => {
165
+ vi.clearAllMocks();
166
+
167
+ // Create mock Supabase client
168
+ mockSupabaseClient = createMockSupabaseClient();
169
+
170
+ // Mock RPC function for getting events
171
+ mockSupabaseClient.rpc = vi.fn().mockImplementation((functionName: string, params: any) => {
172
+ if (functionName === 'data_user_events_get') {
173
+ return Promise.resolve({
174
+ data: mockEvents,
175
+ error: null
176
+ });
177
+ }
178
+ return Promise.resolve({ data: null, error: null });
179
+ });
180
+
181
+ // Mock database queries
182
+ mockSupabaseClient.from = vi.fn().mockReturnValue({
183
+ select: vi.fn().mockReturnValue({
184
+ eq: vi.fn().mockReturnValue({
185
+ order: vi.fn().mockResolvedValue({
186
+ data: mockEvents,
187
+ error: null,
188
+ }),
189
+ }),
190
+ }),
191
+ });
192
+ });
193
+
194
+ afterEach(() => {
195
+ vi.restoreAllMocks();
196
+ localStorage.clear();
197
+ sessionStorage.clear();
198
+ });
199
+
200
+ describe('Rendering', () => {
201
+ it('renders with children', () => {
202
+ render(
203
+ <TestWrapper supabaseClient={mockSupabaseClient}>
204
+ <TestComponent />
205
+ </TestWrapper>
206
+ );
207
+
208
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
209
+ });
210
+
211
+ it('renders with default state', () => {
212
+ render(
213
+ <TestWrapper supabaseClient={mockSupabaseClient}>
214
+ <TestComponent />
215
+ </TestWrapper>
216
+ );
217
+
218
+ expect(screen.getByTestId('events-count')).toHaveTextContent('0');
219
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
220
+ expect(screen.getByTestId('error')).toHaveTextContent('no-error');
221
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
222
+ });
223
+
224
+ it('renders without supabase client', () => {
225
+ render(
226
+ <TestWrapper>
227
+ <TestComponent />
228
+ </TestWrapper>
229
+ );
230
+
231
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
232
+ });
233
+ });
234
+
235
+ describe('Event Loading', () => {
236
+ it('loads events successfully', async () => {
237
+ const user = testDataGenerators.user({ id: 'user-1' });
238
+ const session = testDataGenerators.session();
239
+
240
+ render(
241
+ <TestWrapper
242
+ supabaseClient={mockSupabaseClient}
243
+ user={user}
244
+ session={session}
245
+ selectedOrganisation={mockOrganisation}
246
+ >
247
+ <TestComponent />
248
+ </TestWrapper>
249
+ );
250
+
251
+ await waitFor(() => {
252
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
253
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
254
+ expect(screen.getByTestId('error')).toHaveTextContent('no-error');
255
+ }, { timeout: 3000 });
256
+ }, 10000);
257
+
258
+ it('handles loading errors', async () => {
259
+ const user = testDataGenerators.user({ id: 'user-1' });
260
+ const session = testDataGenerators.session();
261
+
262
+ // Mock RPC error
263
+ mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
264
+ if (functionName === 'data_user_events_get') {
265
+ return Promise.resolve({
266
+ data: null,
267
+ error: new Error('Failed to fetch events')
268
+ });
269
+ }
270
+ return Promise.resolve({ data: null, error: null });
271
+ });
272
+
273
+ render(
274
+ <TestWrapper
275
+ supabaseClient={mockSupabaseClient}
276
+ user={user}
277
+ session={session}
278
+ selectedOrganisation={mockOrganisation}
279
+ >
280
+ <TestComponent />
281
+ </TestWrapper>
282
+ );
283
+
284
+ await waitFor(() => {
285
+ expect(screen.getByTestId('error')).toHaveTextContent('Failed to fetch events');
286
+ expect(screen.getByTestId('events-count')).toHaveTextContent('0');
287
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
288
+ }, { timeout: 5000 });
289
+ });
290
+
291
+ it('skips loading when missing dependencies', async () => {
292
+ render(
293
+ <TestWrapper supabaseClient={mockSupabaseClient}>
294
+ <TestComponent />
295
+ </TestWrapper>
296
+ );
297
+
298
+ // Should not load events without user/session/organisation
299
+ expect(screen.getByTestId('events-count')).toHaveTextContent('0');
300
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
301
+ });
302
+
303
+ it('handles empty events array', async () => {
304
+ const user = testDataGenerators.user({ id: 'user-1' });
305
+ const session = testDataGenerators.session();
306
+
307
+ // Mock empty events
308
+ mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
309
+ if (functionName === 'data_user_events_get') {
310
+ return Promise.resolve({
311
+ data: [],
312
+ error: null
313
+ });
314
+ }
315
+ return Promise.resolve({ data: null, error: null });
316
+ });
317
+
318
+ render(
319
+ <TestWrapper
320
+ supabaseClient={mockSupabaseClient}
321
+ user={user}
322
+ session={session}
323
+ selectedOrganisation={mockOrganisation}
324
+ >
325
+ <TestComponent />
326
+ </TestWrapper>
327
+ );
328
+
329
+ await waitFor(() => {
330
+ expect(screen.getByTestId('events-count')).toHaveTextContent('0');
331
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
332
+ }, { timeout: 5000 });
333
+ });
334
+ });
335
+
336
+ describe('Event Selection', () => {
337
+ it('auto-selects next event by date', async () => {
338
+ const user = testDataGenerators.user({ id: 'user-1' });
339
+ const session = testDataGenerators.session();
340
+
341
+ render(
342
+ <TestWrapper
343
+ supabaseClient={mockSupabaseClient}
344
+ user={user}
345
+ session={session}
346
+ selectedOrganisation={mockOrganisation}
347
+ >
348
+ <TestComponent />
349
+ </TestWrapper>
350
+ );
351
+
352
+ await waitFor(() => {
353
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
354
+ expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
355
+ }, { timeout: 5000 });
356
+
357
+ // Should auto-select the next event (event-1, which is the earliest future event)
358
+ await waitFor(() => {
359
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
360
+ expect(screen.getByTestId('selected-event-id')).toHaveTextContent('event-1');
361
+ });
362
+ });
363
+
364
+ it('allows manual event selection', async () => {
365
+ const user = testDataGenerators.user({ id: 'user-1' });
366
+ const session = testDataGenerators.session();
367
+
368
+ render(
369
+ <TestWrapper
370
+ supabaseClient={mockSupabaseClient}
371
+ user={user}
372
+ session={session}
373
+ selectedOrganisation={mockOrganisation}
374
+ >
375
+ <TestComponent />
376
+ </TestWrapper>
377
+ );
378
+
379
+ await waitFor(() => {
380
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
381
+ }, { timeout: 5000 });
382
+
383
+ // Select first event manually
384
+ const selectButton = screen.getByText('Select First Event');
385
+ await userEvent.click(selectButton);
386
+
387
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
388
+ expect(screen.getByTestId('selected-event-id')).toHaveTextContent('event-1');
389
+ });
390
+
391
+ it('allows clearing event selection', async () => {
392
+ const user = testDataGenerators.user({ id: 'user-1' });
393
+ const session = testDataGenerators.session();
394
+
395
+ render(
396
+ <TestWrapper
397
+ supabaseClient={mockSupabaseClient}
398
+ user={user}
399
+ session={session}
400
+ selectedOrganisation={mockOrganisation}
401
+ >
402
+ <TestComponent />
403
+ </TestWrapper>
404
+ );
405
+
406
+ await waitFor(() => {
407
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
408
+ }, { timeout: 5000 });
409
+
410
+ // Clear event selection
411
+ const clearButton = screen.getByText('Clear Event');
412
+ await userEvent.click(clearButton);
413
+
414
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
415
+ expect(screen.getByTestId('selected-event-id')).toHaveTextContent('no-id');
416
+ });
417
+
418
+ it('validates event organisation before selection', async () => {
419
+ const user = testDataGenerators.user({ id: 'user-1' });
420
+ const session = testDataGenerators.session();
421
+
422
+ // Create event with different organisation
423
+ const invalidEvent = {
424
+ ...mockEvents[0],
425
+ organisation_id: 'different-org'
426
+ };
427
+
428
+ render(
429
+ <TestWrapper
430
+ supabaseClient={mockSupabaseClient}
431
+ user={user}
432
+ session={session}
433
+ selectedOrganisation={mockOrganisation}
434
+ >
435
+ <TestComponent />
436
+ </TestWrapper>
437
+ );
438
+
439
+ await waitFor(() => {
440
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
441
+ }, { timeout: 5000 });
442
+
443
+ // Try to select invalid event
444
+ const TestInvalidSelectionComponent = () => {
445
+ const { setSelectedEvent } = useEvents();
446
+
447
+ const handleInvalidSelection = () => {
448
+ setSelectedEvent(invalidEvent as any);
449
+ };
450
+
451
+ return (
452
+ <button onClick={handleInvalidSelection}>
453
+ Select Invalid Event
454
+ </button>
455
+ );
456
+ };
457
+
458
+ render(
459
+ <TestWrapper
460
+ supabaseClient={mockSupabaseClient}
461
+ user={user}
462
+ session={session}
463
+ selectedOrganisation={mockOrganisation}
464
+ >
465
+ <TestInvalidSelectionComponent />
466
+ </TestWrapper>
467
+ );
468
+
469
+ const invalidButton = screen.getByText('Select Invalid Event');
470
+ await userEvent.click(invalidButton);
471
+
472
+ // Should not select the invalid event
473
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
474
+ });
475
+ });
476
+
477
+ describe('Event Persistence', () => {
478
+ it('persists event selection in sessionStorage', async () => {
479
+ const user = testDataGenerators.user({ id: 'user-1' });
480
+ const session = testDataGenerators.session();
481
+
482
+ render(
483
+ <TestWrapper
484
+ supabaseClient={mockSupabaseClient}
485
+ user={user}
486
+ session={session}
487
+ selectedOrganisation={mockOrganisation}
488
+ >
489
+ <TestComponent />
490
+ </TestWrapper>
491
+ );
492
+
493
+ await waitFor(() => {
494
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
495
+ }, { timeout: 5000 });
496
+
497
+ // Select an event
498
+ const selectButton = screen.getByText('Select First Event');
499
+ await userEvent.click(selectButton);
500
+
501
+ // Check that it's persisted
502
+ expect(sessionStorage.getItem('pace-core-selected-event')).toBe('event-1');
503
+ expect(localStorage.getItem('pace-core-selected-event')).toBe('event-1');
504
+ });
505
+
506
+ it('restores persisted event selection', async () => {
507
+ const user = testDataGenerators.user({ id: 'user-1' });
508
+ const session = testDataGenerators.session();
509
+
510
+ // Set persisted event
511
+ sessionStorage.setItem('pace-core-selected-event', 'event-2');
512
+
513
+ render(
514
+ <TestWrapper
515
+ supabaseClient={mockSupabaseClient}
516
+ user={user}
517
+ session={session}
518
+ selectedOrganisation={mockOrganisation}
519
+ >
520
+ <TestComponent />
521
+ </TestWrapper>
522
+ );
523
+
524
+ await waitFor(() => {
525
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
526
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 2');
527
+ }, { timeout: 5000 });
528
+ });
529
+
530
+ it('clears invalid persisted event', async () => {
531
+ const user = testDataGenerators.user({ id: 'user-1' });
532
+ const session = testDataGenerators.session();
533
+
534
+ // Set invalid persisted event
535
+ sessionStorage.setItem('pace-core-selected-event', 'invalid-event-id');
536
+
537
+ render(
538
+ <TestWrapper
539
+ supabaseClient={mockSupabaseClient}
540
+ user={user}
541
+ session={session}
542
+ selectedOrganisation={mockOrganisation}
543
+ >
544
+ <TestComponent />
545
+ </TestWrapper>
546
+ );
547
+
548
+ await waitFor(() => {
549
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
550
+ }, { timeout: 5000 });
551
+
552
+ // Should clear invalid persisted event and auto-select next
553
+ expect(sessionStorage.getItem('pace-core-selected-event')).toBeNull();
554
+ expect(localStorage.getItem('pace-core-selected-event')).toBeNull();
555
+ });
556
+ });
557
+
558
+ describe('Refresh Functionality', () => {
559
+ it('refreshes events', async () => {
560
+ const user = testDataGenerators.user({ id: 'user-1' });
561
+ const session = testDataGenerators.session();
562
+
563
+ render(
564
+ <TestWrapper
565
+ supabaseClient={mockSupabaseClient}
566
+ user={user}
567
+ session={session}
568
+ selectedOrganisation={mockOrganisation}
569
+ >
570
+ <TestComponent />
571
+ </TestWrapper>
572
+ );
573
+
574
+ await waitFor(() => {
575
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
576
+ }, { timeout: 5000 });
577
+
578
+ // Refresh events
579
+ const refreshButton = screen.getByText('Refresh Events');
580
+ await userEvent.click(refreshButton);
581
+
582
+ // Should reload events
583
+ await waitFor(() => {
584
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
585
+ });
586
+ });
587
+ });
588
+
589
+ describe('Helper Functions', () => {
590
+ it('correctly identifies next event by date', async () => {
591
+ const user = testDataGenerators.user({ id: 'user-1' });
592
+ const session = testDataGenerators.session();
593
+
594
+ render(
595
+ <TestWrapper
596
+ supabaseClient={mockSupabaseClient}
597
+ user={user}
598
+ session={session}
599
+ selectedOrganisation={mockOrganisation}
600
+ >
601
+ <TestComponent />
602
+ </TestWrapper>
603
+ );
604
+
605
+ await waitFor(() => {
606
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
607
+ }, { timeout: 5000 });
608
+
609
+ // Should select the earliest future event (event-1)
610
+ await waitFor(() => {
611
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
612
+ });
613
+ });
614
+
615
+ it('handles events with no date', async () => {
616
+ const user = testDataGenerators.user({ id: 'user-1' });
617
+ const session = testDataGenerators.session();
618
+
619
+ // Mock events with no date
620
+ const eventsWithoutDate = [
621
+ {
622
+ event_id: 'event-no-date',
623
+ event_name: 'Event Without Date',
624
+ event_date: null,
625
+ event_venue: 'Test Venue',
626
+ event_participants: 100,
627
+ event_colours: { primary: '#ff0000', secondary: '#00ff00' },
628
+ organisation_id: 'org-1',
629
+ is_visible: true,
630
+ }
631
+ ];
632
+
633
+ mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
634
+ if (functionName === 'data_user_events_get') {
635
+ return Promise.resolve({
636
+ data: eventsWithoutDate,
637
+ error: null
638
+ });
639
+ }
640
+ return Promise.resolve({ data: null, error: null });
641
+ });
642
+
643
+ render(
644
+ <TestWrapper
645
+ supabaseClient={mockSupabaseClient}
646
+ user={user}
647
+ session={session}
648
+ selectedOrganisation={mockOrganisation}
649
+ >
650
+ <TestComponent />
651
+ </TestWrapper>
652
+ );
653
+
654
+ await waitFor(() => {
655
+ expect(screen.getByTestId('events-count')).toHaveTextContent('1');
656
+ expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
657
+ }, { timeout: 5000 });
658
+ });
659
+ });
660
+
661
+ describe('Hook Usage', () => {
662
+ it('provides useEvents hook', () => {
663
+ render(
664
+ <TestWrapper supabaseClient={mockSupabaseClient}>
665
+ <TestComponent />
666
+ </TestWrapper>
667
+ );
668
+
669
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
670
+ });
671
+
672
+ it('throws error when hook is used outside provider', () => {
673
+ // Suppress console.error for this test
674
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
675
+
676
+ expect(() => {
677
+ render(<TestComponent />);
678
+ }).toThrow('useEvents must be used within an EventProvider');
679
+
680
+ consoleSpy.mockRestore();
681
+ });
682
+ });
683
+
684
+ describe('Error Handling', () => {
685
+ it('handles Supabase client errors gracefully', async () => {
686
+ const user = testDataGenerators.user({ id: 'user-1' });
687
+ const session = testDataGenerators.session();
688
+
689
+ // Mock RPC error
690
+ mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
691
+ if (functionName === 'data_user_events_get') {
692
+ return Promise.reject(new Error('Network error'));
693
+ }
694
+ return Promise.resolve({ data: null, error: null });
695
+ });
696
+
697
+ render(
698
+ <TestWrapper
699
+ supabaseClient={mockSupabaseClient}
700
+ user={user}
701
+ session={session}
702
+ selectedOrganisation={mockOrganisation}
703
+ >
704
+ <TestComponent />
705
+ </TestWrapper>
706
+ );
707
+
708
+ await waitFor(() => {
709
+ expect(screen.getByTestId('error')).toHaveTextContent('Network error');
710
+ expect(screen.getByTestId('events-count')).toHaveTextContent('0');
711
+ }, { timeout: 5000 });
712
+ });
713
+
714
+ it('handles missing Supabase client', () => {
715
+ expect(() => {
716
+ render(
717
+ <TestWrapper>
718
+ <TestComponent />
719
+ </TestWrapper>
720
+ );
721
+ }).not.toThrow();
722
+ });
723
+
724
+ it('handles organisation context errors', async () => {
725
+ const user = testDataGenerators.user({ id: 'user-1' });
726
+ const session = testDataGenerators.session();
727
+
728
+ // Mock organisation context error
729
+ mockOrganisationState.ensureOrganisationContext.mockRejectedValue(new Error('Organisation context error'));
730
+
731
+ render(
732
+ <TestWrapper
733
+ supabaseClient={mockSupabaseClient}
734
+ user={user}
735
+ session={session}
736
+ selectedOrganisation={mockOrganisation}
737
+ >
738
+ <TestComponent />
739
+ </TestWrapper>
740
+ );
741
+
742
+ // Should still load events even if organisation context fails
743
+ await waitFor(() => {
744
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
745
+ }, { timeout: 5000 });
746
+ });
747
+ });
748
+
749
+ describe('Context Value Stability', () => {
750
+ it('maintains stable context value references', () => {
751
+ const TestStabilityComponent = () => {
752
+ const { setSelectedEvent, refreshEvents } = useEvents();
753
+ const [renderCount, setRenderCount] = React.useState(0);
754
+
755
+ React.useEffect(() => {
756
+ setRenderCount(prev => prev + 1);
757
+ });
758
+
759
+ return (
760
+ <div>
761
+ <div data-testid="renderCount">{renderCount}</div>
762
+ <div data-testid="hasStableSetSelectedEvent">{typeof setSelectedEvent === 'function' ? 'true' : 'false'}</div>
763
+ <div data-testid="hasStableRefreshEvents">{typeof refreshEvents === 'function' ? 'true' : 'false'}</div>
764
+ </div>
765
+ );
766
+ };
767
+
768
+ render(
769
+ <TestWrapper supabaseClient={mockSupabaseClient}>
770
+ <TestStabilityComponent />
771
+ </TestWrapper>
772
+ );
773
+
774
+ // Should render multiple times but maintain stable function references
775
+ expect(screen.getByTestId('hasStableSetSelectedEvent')).toHaveTextContent('true');
776
+ expect(screen.getByTestId('hasStableRefreshEvents')).toHaveTextContent('true');
777
+ });
778
+ });
779
+
780
+ describe('Cleanup', () => {
781
+ it('handles component unmount gracefully', () => {
782
+ const { unmount } = render(
783
+ <TestWrapper supabaseClient={mockSupabaseClient}>
784
+ <div>Test content</div>
785
+ </TestWrapper>
786
+ );
787
+
788
+ // Should not throw errors on unmount
789
+ expect(() => unmount()).not.toThrow();
790
+ });
791
+
792
+ it('clears persisted data when event is cleared', async () => {
793
+ const user = testDataGenerators.user({ id: 'user-1' });
794
+ const session = testDataGenerators.session();
795
+
796
+ // Set persisted event
797
+ sessionStorage.setItem('pace-core-selected-event', 'event-1');
798
+ localStorage.setItem('pace-core-selected-event', 'event-1');
799
+
800
+ render(
801
+ <TestWrapper
802
+ supabaseClient={mockSupabaseClient}
803
+ user={user}
804
+ session={session}
805
+ selectedOrganisation={mockOrganisation}
806
+ >
807
+ <TestComponent />
808
+ </TestWrapper>
809
+ );
810
+
811
+ await waitFor(() => {
812
+ expect(screen.getByTestId('events-count')).toHaveTextContent('3');
813
+ }, { timeout: 5000 });
814
+
815
+ // Clear event
816
+ const clearButton = screen.getByText('Clear Event');
817
+ await userEvent.click(clearButton);
818
+
819
+ // Persisted data should be cleared
820
+ expect(sessionStorage.getItem('pace-core-selected-event')).toBeNull();
821
+ expect(localStorage.getItem('pace-core-selected-event')).toBeNull();
822
+ });
823
+ });
824
+ });