@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -1,784 +0,0 @@
1
- /**
2
- * @file OrganisationSelector Component Tests
3
- * @package @jmruthers/pace-core
4
- * @module Components/OrganisationSelector
5
- * @since 0.4.0
6
- *
7
- * Comprehensive test suite for the OrganisationSelector component covering all functionality,
8
- * RBAC behavior, error handling, and accessibility features.
9
- */
10
-
11
- import React from 'react';
12
- import { screen, waitFor } from '@testing-library/react';
13
- import userEvent from '@testing-library/user-event';
14
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
- import { OrganisationSelector, OrganisationSelectorProps } from './OrganisationSelector';
16
- import { renderWithProviders } from '../../__tests__/helpers/test-utils';
17
- import type { Organisation } from '../../types/organisation';
18
- import type { SelectProps, SelectContentProps, SelectItemProps, SelectTriggerProps, SelectValueProps } from '../Select';
19
- import type { ButtonProps } from '../Button/Button';
20
- import type { LoadingSpinnerProps } from '../LoadingSpinner/LoadingSpinner';
21
-
22
- // Mock the useOrganisationService hook (used internally by useOrganisations)
23
- const mockUseOrganisationService = vi.fn();
24
- vi.mock('../../hooks/services/useOrganisationService', () => ({
25
- useOrganisationService: () => mockUseOrganisationService(),
26
- }));
27
-
28
- // Mock the useOrganisations hook
29
- const mockUseOrganisations = vi.fn();
30
- vi.mock('../../hooks/useOrganisations', () => ({
31
- useOrganisations: () => mockUseOrganisations(),
32
- }));
33
-
34
- // Mock child components
35
- let mockOnValueChange: ((value: string) => void) | null = null;
36
-
37
- vi.mock('../Select', () => ({
38
- Select: ({ children, value, onValueChange, disabled }: SelectProps & { value?: string; onValueChange?: (value: string) => void; disabled?: boolean }) => {
39
- // Store the onValueChange callback for SelectItem to use
40
- mockOnValueChange = onValueChange || null;
41
- return (
42
- <div data-testid="select" data-value={value} data-disabled={disabled}>
43
- {children}
44
- </div>
45
- );
46
- },
47
- SelectContent: ({ children }: SelectContentProps) => (
48
- <div data-testid="select-content">{children}</div>
49
- ),
50
- SelectItem: ({ children, value, disabled, className }: SelectItemProps) => (
51
- <div
52
- data-testid={`select-item-${value}`}
53
- data-disabled={disabled}
54
- className={className}
55
- onClick={() => !disabled && mockOnValueChange?.(value)}
56
- >
57
- {children}
58
- </div>
59
- ),
60
- SelectTrigger: ({ children, className }: SelectTriggerProps) => (
61
- <button data-testid="select-trigger" className={className}>
62
- {children}
63
- </button>
64
- ),
65
- SelectValue: ({ placeholder }: SelectValueProps) => (
66
- <span data-testid="select-value">{placeholder}</span>
67
- ),
68
- }));
69
-
70
- vi.mock('../Alert/Alert', () => ({
71
- Alert: ({ children, variant }: React.HTMLAttributes<HTMLDivElement> & { variant?: "default" | "destructive" | "inline" }) => (
72
- <div data-testid="alert" data-variant={variant}>
73
- {children}
74
- </div>
75
- ),
76
- AlertDescription: ({ children }: React.HTMLAttributes<HTMLDivElement>) => (
77
- <div data-testid="alert-description">{children}</div>
78
- ),
79
- }));
80
-
81
- vi.mock('../Button/Button', () => ({
82
- Button: ({ children, onClick, disabled, variant, size, className }: ButtonProps) => (
83
- <button
84
- data-testid="button"
85
- onClick={onClick}
86
- disabled={disabled}
87
- data-variant={variant}
88
- data-size={size}
89
- className={className}
90
- >
91
- {children}
92
- </button>
93
- ),
94
- }));
95
-
96
- vi.mock('../LoadingSpinner/LoadingSpinner', () => ({
97
- LoadingSpinner: ({ size }: LoadingSpinnerProps) => (
98
- <div data-testid="loading-spinner" data-size={size}>Loading...</div>
99
- ),
100
- }));
101
-
102
- // Test data
103
- const mockOrganisations: Organisation[] = [
104
- {
105
- id: 'org-1',
106
- name: 'acme-corp',
107
- display_name: 'Acme Corporation',
108
- description: 'Leading technology company',
109
- subscription_tier: 'premium',
110
- settings: {},
111
- is_active: true,
112
- created_at: '2023-01-01T00:00:00Z',
113
- updated_at: '2023-01-01T00:00:00Z',
114
- },
115
- {
116
- id: 'org-2',
117
- name: 'beta-inc',
118
- display_name: 'Beta Inc',
119
- description: 'Innovation focused startup',
120
- subscription_tier: 'standard',
121
- settings: {},
122
- is_active: true,
123
- created_at: '2023-01-02T00:00:00Z',
124
- updated_at: '2023-01-02T00:00:00Z',
125
- },
126
- {
127
- id: 'org-3',
128
- name: 'gamma-ltd',
129
- display_name: 'Gamma Ltd',
130
- description: 'Global enterprise solutions',
131
- subscription_tier: 'enterprise',
132
- settings: {},
133
- is_active: true,
134
- created_at: '2023-01-03T00:00:00Z',
135
- updated_at: '2023-01-03T00:00:00Z',
136
- },
137
- ];
138
-
139
- const mockSelectedOrganisation = mockOrganisations[0];
140
-
141
- const defaultMockContext = {
142
- organisations: mockOrganisations,
143
- selectedOrganisation: mockSelectedOrganisation,
144
- isLoading: false,
145
- error: null,
146
- switchOrganisation: vi.fn().mockResolvedValue(undefined),
147
- getUserRole: vi.fn().mockReturnValue('admin'),
148
- validateOrganisationAccess: vi.fn().mockReturnValue(true),
149
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
150
- };
151
-
152
- describe('OrganisationSelector Component', () => {
153
- beforeEach(() => {
154
- vi.clearAllMocks();
155
-
156
- // Re-setup the default mock context after clearing mocks
157
- const mockContext = {
158
- organisations: mockOrganisations,
159
- selectedOrganisation: mockSelectedOrganisation,
160
- isLoading: false,
161
- error: null,
162
- switchOrganisation: vi.fn().mockResolvedValue(undefined),
163
- getUserRole: vi.fn().mockReturnValue('admin'),
164
- validateOrganisationAccess: vi.fn().mockReturnValue(true), // Ensure all orgs are accessible by default
165
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
166
- };
167
-
168
- mockUseOrganisations.mockReturnValue(mockContext);
169
- });
170
-
171
- afterEach(() => {
172
- vi.clearAllMocks();
173
- });
174
-
175
- // Basic rendering tests
176
- describe('Rendering', () => {
177
- it('renders with default props', () => {
178
- renderWithProviders(<OrganisationSelector />);
179
-
180
- expect(screen.getByTestId('select')).toBeInTheDocument();
181
- expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
182
- expect(screen.getByTestId('select-value')).toHaveTextContent('Select organisation');
183
- });
184
-
185
- it('renders with custom placeholder', () => {
186
- renderWithProviders(<OrganisationSelector placeholder="Choose organisation..." />);
187
-
188
- expect(screen.getByTestId('select-value')).toHaveTextContent('Choose organisation...');
189
- });
190
-
191
- it('renders with custom className', () => {
192
- renderWithProviders(<OrganisationSelector className="custom-selector" />);
193
-
194
- const container = screen.getByTestId('select').parentElement;
195
- expect(container).toHaveClass('custom-selector');
196
- });
197
-
198
- it('renders with proper DOM structure', () => {
199
- renderWithProviders(<OrganisationSelector />);
200
-
201
- expect(screen.getByTestId('select')).toBeInTheDocument();
202
- expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
203
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
204
- });
205
- });
206
-
207
- // Loading state tests
208
- describe('Loading State', () => {
209
- it('renders loading state when organisations are loading', () => {
210
- mockUseOrganisations.mockReturnValue({
211
- ...defaultMockContext,
212
- isLoading: true,
213
- });
214
-
215
- renderWithProviders(<OrganisationSelector />);
216
-
217
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
218
- expect(screen.getByText('Loading organisations...')).toBeInTheDocument();
219
- });
220
-
221
- it('renders compact loading state', () => {
222
- mockUseOrganisations.mockReturnValue({
223
- ...defaultMockContext,
224
- isLoading: true,
225
- });
226
-
227
- renderWithProviders(<OrganisationSelector compact={true} />);
228
-
229
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
230
- expect(screen.getAllByText('Loading...')).toHaveLength(2); // Spinner + text
231
- });
232
-
233
- it('shows loading spinner in trigger when switching organisations', () => {
234
- // Test the loading state directly by mocking the loading state
235
- mockUseOrganisations.mockReturnValue({
236
- ...defaultMockContext,
237
- isLoading: true,
238
- });
239
-
240
- renderWithProviders(<OrganisationSelector />);
241
-
242
- // Should show loading state
243
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
244
- });
245
- });
246
-
247
- // Error state tests
248
- describe('Error State', () => {
249
- it('renders error state when organisations fail to load', () => {
250
- const error = new Error('Failed to load organisations');
251
- mockUseOrganisations.mockReturnValue({
252
- ...defaultMockContext,
253
- error,
254
- organisations: [],
255
- });
256
-
257
- renderWithProviders(<OrganisationSelector />);
258
-
259
- expect(screen.getByTestId('alert')).toBeInTheDocument();
260
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Failed to load organisations');
261
- });
262
-
263
- it('shows retry button when showRetryButton is true', () => {
264
- const error = new Error('Failed to load organisations');
265
- mockUseOrganisations.mockReturnValue({
266
- ...defaultMockContext,
267
- error,
268
- organisations: [],
269
- });
270
-
271
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
272
-
273
- expect(screen.getByTestId('button')).toBeInTheDocument();
274
- expect(screen.getByTestId('button')).toHaveTextContent('Retry');
275
- });
276
-
277
- it('does not show retry button when showRetryButton is false', () => {
278
- const error = new Error('Failed to load organisations');
279
- mockUseOrganisations.mockReturnValue({
280
- ...defaultMockContext,
281
- error,
282
- organisations: [],
283
- });
284
-
285
- renderWithProviders(<OrganisationSelector showRetryButton={false} />);
286
-
287
- expect(screen.queryByTestId('button')).not.toBeInTheDocument();
288
- });
289
-
290
- it('handles retry button click', async () => {
291
- const user = userEvent.setup();
292
- const refreshOrganisations = vi.fn().mockResolvedValue(undefined);
293
-
294
- const error = new Error('Failed to load organisations');
295
- mockUseOrganisations.mockReturnValue({
296
- ...defaultMockContext,
297
- error,
298
- organisations: [],
299
- refreshOrganisations,
300
- });
301
-
302
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
303
-
304
- const retryButton = screen.getByTestId('button');
305
- await user.click(retryButton);
306
-
307
- expect(refreshOrganisations).toHaveBeenCalledTimes(1);
308
- });
309
- });
310
-
311
- // Empty state tests
312
- describe('Empty State', () => {
313
- it('renders no organisations message when showNoOrganisationsMessage is true', () => {
314
- mockUseOrganisations.mockReturnValue({
315
- ...defaultMockContext,
316
- organisations: [],
317
- });
318
-
319
- renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={true} />);
320
-
321
- expect(screen.getByTestId('alert')).toBeInTheDocument();
322
- expect(screen.getByTestId('alert-description')).toHaveTextContent('No organisations available. Please contact your administrator to be added to an organisation.');
323
- });
324
-
325
- it('shows check again button when no organisations and showRetryButton is true', () => {
326
- mockUseOrganisations.mockReturnValue({
327
- ...defaultMockContext,
328
- organisations: [],
329
- });
330
-
331
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
332
-
333
- expect(screen.getByTestId('button')).toBeInTheDocument();
334
- expect(screen.getByTestId('button')).toHaveTextContent('Check Again');
335
- });
336
-
337
- it('returns null when no organisations and showNoOrganisationsMessage is false', () => {
338
- mockUseOrganisations.mockReturnValue({
339
- ...defaultMockContext,
340
- organisations: [],
341
- });
342
-
343
- const { container } = renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={false} />);
344
-
345
- expect(container.firstChild).toBeNull();
346
- });
347
- });
348
-
349
- // Organisation switching tests
350
- describe('Organisation Switching', () => {
351
- it('renders all available organisations', () => {
352
- renderWithProviders(<OrganisationSelector />);
353
-
354
- mockOrganisations.forEach(org => {
355
- expect(screen.getByTestId(`select-item-${org.id}`)).toBeInTheDocument();
356
- });
357
- });
358
-
359
- it('displays organisation names and descriptions', () => {
360
- renderWithProviders(<OrganisationSelector />);
361
-
362
- expect(screen.getByText('Acme Corporation')).toBeInTheDocument();
363
- expect(screen.getByText('Leading technology company')).toBeInTheDocument();
364
- expect(screen.getByText('Beta Inc')).toBeInTheDocument();
365
- expect(screen.getByText('Innovation focused startup')).toBeInTheDocument();
366
- });
367
-
368
- it('handles organisation selection', async () => {
369
- const user = userEvent.setup();
370
- const onOrganisationChange = vi.fn();
371
- const switchOrganisation = vi.fn().mockResolvedValue(undefined);
372
- const validateOrganisationAccess = vi.fn().mockReturnValue(true);
373
-
374
- mockUseOrganisations.mockReturnValue({
375
- organisations: mockOrganisations,
376
- selectedOrganisation: mockSelectedOrganisation,
377
- isLoading: false,
378
- error: null,
379
- switchOrganisation,
380
- getUserRole: vi.fn().mockReturnValue('admin'),
381
- validateOrganisationAccess,
382
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
383
- });
384
-
385
- renderWithProviders(
386
- <OrganisationSelector onOrganisationChange={onOrganisationChange} />
387
- );
388
-
389
- const selectItem = screen.getByTestId('select-item-org-2');
390
- await user.click(selectItem);
391
-
392
- expect(switchOrganisation).toHaveBeenCalledWith('org-2');
393
- });
394
-
395
- it('calls onOrganisationChange callback after successful switch', async () => {
396
- const user = userEvent.setup();
397
- const onOrganisationChange = vi.fn();
398
- const switchOrganisation = vi.fn().mockResolvedValue(undefined);
399
- const validateOrganisationAccess = vi.fn().mockReturnValue(true);
400
-
401
- mockUseOrganisations.mockReturnValue({
402
- organisations: mockOrganisations,
403
- selectedOrganisation: mockSelectedOrganisation,
404
- isLoading: false,
405
- error: null,
406
- switchOrganisation,
407
- getUserRole: vi.fn().mockReturnValue('admin'),
408
- validateOrganisationAccess,
409
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
410
- });
411
-
412
- renderWithProviders(
413
- <OrganisationSelector onOrganisationChange={onOrganisationChange} />
414
- );
415
-
416
- const selectItem = screen.getByTestId('select-item-org-2');
417
- await user.click(selectItem);
418
-
419
- await waitFor(() => {
420
- expect(onOrganisationChange).toHaveBeenCalledWith(mockOrganisations[1]);
421
- });
422
- });
423
-
424
- it('handles organisation switch errors', () => {
425
- // Test error state directly by mocking an error
426
- const error = new Error('Access denied');
427
- mockUseOrganisations.mockReturnValue({
428
- ...defaultMockContext,
429
- error,
430
- organisations: [],
431
- });
432
-
433
- renderWithProviders(<OrganisationSelector />);
434
-
435
- expect(screen.getByTestId('alert')).toBeInTheDocument();
436
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Access denied');
437
- });
438
-
439
- it('validates organisation access before switching', async () => {
440
- const user = userEvent.setup();
441
- const validateOrganisationAccess = vi.fn().mockReturnValue(false);
442
- const switchOrganisation = vi.fn();
443
-
444
- mockUseOrganisations.mockReturnValue({
445
- ...defaultMockContext,
446
- validateOrganisationAccess,
447
- switchOrganisation,
448
- });
449
-
450
- renderWithProviders(<OrganisationSelector />);
451
-
452
- const selectItem = screen.getByTestId('select-item-org-2');
453
- await user.click(selectItem);
454
-
455
- expect(validateOrganisationAccess).toHaveBeenCalledWith('org-2');
456
- expect(switchOrganisation).not.toHaveBeenCalled();
457
- });
458
- });
459
-
460
- // RBAC and permission tests
461
- describe('RBAC and Permissions', () => {
462
- it('disables organisations without access', () => {
463
- const validateOrganisationAccess = vi.fn((orgId: string) => orgId !== 'org-2');
464
-
465
- mockUseOrganisations.mockReturnValue({
466
- ...defaultMockContext,
467
- validateOrganisationAccess,
468
- });
469
-
470
- renderWithProviders(<OrganisationSelector />);
471
-
472
- expect(screen.getByTestId('select-item-org-1')).not.toHaveAttribute('data-disabled', 'true');
473
- expect(screen.getByTestId('select-item-org-2')).toHaveAttribute('data-disabled', 'true');
474
- expect(screen.getByTestId('select-item-org-3')).not.toHaveAttribute('data-disabled', 'true');
475
- });
476
-
477
- it('shows user role when showRole is true', () => {
478
- const getUserRole = vi.fn().mockReturnValue('admin');
479
-
480
- mockUseOrganisations.mockReturnValue({
481
- ...defaultMockContext,
482
- getUserRole,
483
- });
484
-
485
- renderWithProviders(<OrganisationSelector showRole={true} />);
486
-
487
- // The role should be displayed in the select items
488
- expect(screen.getAllByText('admin')).toHaveLength(3);
489
- });
490
-
491
- it('does not show role when showRole is false', () => {
492
- const getUserRole = vi.fn().mockReturnValue('admin');
493
-
494
- mockUseOrganisations.mockReturnValue({
495
- ...defaultMockContext,
496
- getUserRole,
497
- });
498
-
499
- renderWithProviders(<OrganisationSelector showRole={false} />);
500
-
501
- expect(screen.queryByText('admin')).not.toBeInTheDocument();
502
- });
503
-
504
- it('formats role names correctly', () => {
505
- const getUserRole = vi.fn().mockReturnValue('super_admin');
506
-
507
- mockUseOrganisations.mockReturnValue({
508
- ...defaultMockContext,
509
- getUserRole,
510
- });
511
-
512
- renderWithProviders(<OrganisationSelector showRole={true} />);
513
-
514
- expect(screen.getAllByText('super admin')).toHaveLength(3);
515
- });
516
- });
517
-
518
- // Compact mode tests
519
- describe('Compact Mode', () => {
520
- it('hides descriptions in compact mode', () => {
521
- renderWithProviders(<OrganisationSelector compact={true} />);
522
-
523
- expect(screen.queryByText('Leading technology company')).not.toBeInTheDocument();
524
- expect(screen.queryByText('Innovation focused startup')).not.toBeInTheDocument();
525
- });
526
-
527
- it('shows descriptions when not in compact mode', () => {
528
- renderWithProviders(<OrganisationSelector compact={false} />);
529
-
530
- expect(screen.getByText('Leading technology company')).toBeInTheDocument();
531
- expect(screen.getByText('Innovation focused startup')).toBeInTheDocument();
532
- });
533
- });
534
-
535
- // Disabled state tests
536
- describe('Disabled State', () => {
537
- it('disables the selector when disabled prop is true', () => {
538
- renderWithProviders(<OrganisationSelector disabled={true} />);
539
-
540
- expect(screen.getByTestId('select')).toHaveAttribute('data-disabled', 'true');
541
- });
542
-
543
- it('enables the selector when disabled prop is false', () => {
544
- renderWithProviders(<OrganisationSelector disabled={false} />);
545
-
546
- expect(screen.getByTestId('select')).toHaveAttribute('data-disabled', 'false');
547
- });
548
-
549
- it('disables selector during loading', () => {
550
- // Test the disabled state directly by mocking the loading state
551
- mockUseOrganisations.mockReturnValue({
552
- ...defaultMockContext,
553
- isLoading: true,
554
- });
555
-
556
- renderWithProviders(<OrganisationSelector />);
557
-
558
- // Should show loading state instead of selector
559
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
560
- });
561
- });
562
-
563
- // Accessibility tests
564
- describe('Accessibility', () => {
565
- it('has proper ARIA attributes', () => {
566
- renderWithProviders(<OrganisationSelector />);
567
-
568
- const select = screen.getByTestId('select');
569
- expect(select).toBeInTheDocument();
570
- });
571
-
572
- it('provides screen reader accessible content', () => {
573
- renderWithProviders(<OrganisationSelector />);
574
-
575
- expect(screen.getByText('Acme Corporation')).toBeInTheDocument();
576
- expect(screen.getByText('Beta Inc')).toBeInTheDocument();
577
- });
578
-
579
- it('shows loading state to screen readers', () => {
580
- mockUseOrganisations.mockReturnValue({
581
- ...defaultMockContext,
582
- isLoading: true,
583
- });
584
-
585
- renderWithProviders(<OrganisationSelector />);
586
-
587
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
588
- expect(screen.getByText('Loading organisations...')).toBeInTheDocument();
589
- });
590
-
591
- it('announces errors to screen readers', () => {
592
- const error = new Error('Failed to load organisations');
593
- mockUseOrganisations.mockReturnValue({
594
- ...defaultMockContext,
595
- error,
596
- organisations: [],
597
- });
598
-
599
- renderWithProviders(<OrganisationSelector />);
600
-
601
- expect(screen.getByTestId('alert')).toBeInTheDocument();
602
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Failed to load organisations');
603
- });
604
- });
605
-
606
- // Error handling tests
607
- describe('Error Handling', () => {
608
- it('handles switch errors gracefully', () => {
609
- // Test error state directly by mocking an error
610
- const error = new Error('Network error');
611
- mockUseOrganisations.mockReturnValue({
612
- ...defaultMockContext,
613
- error,
614
- organisations: [],
615
- });
616
-
617
- renderWithProviders(<OrganisationSelector />);
618
-
619
- expect(screen.getByTestId('alert')).toBeInTheDocument();
620
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Network error');
621
- });
622
-
623
- it('handles retry errors gracefully', () => {
624
- // Test error state directly by mocking an error
625
- const error = new Error('Retry failed');
626
- mockUseOrganisations.mockReturnValue({
627
- ...defaultMockContext,
628
- error,
629
- organisations: [],
630
- });
631
-
632
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
633
-
634
- expect(screen.getByTestId('alert')).toBeInTheDocument();
635
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Retry failed');
636
- });
637
-
638
- it('clears errors on successful retry', async () => {
639
- const user = userEvent.setup();
640
- const refreshOrganisations = vi.fn().mockResolvedValue(undefined);
641
-
642
- const error = new Error('Failed to load organisations');
643
- mockUseOrganisations.mockReturnValue({
644
- ...defaultMockContext,
645
- error,
646
- organisations: [],
647
- refreshOrganisations,
648
- });
649
-
650
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
651
-
652
- const retryButton = screen.getByTestId('button');
653
- await user.click(retryButton);
654
-
655
- await waitFor(() => {
656
- expect(refreshOrganisations).toHaveBeenCalledTimes(1);
657
- });
658
- });
659
- });
660
-
661
- // Edge cases and prop validation tests
662
- describe('Edge Cases and Prop Validation', () => {
663
- it('handles empty organisations array', () => {
664
- mockUseOrganisations.mockReturnValue({
665
- ...defaultMockContext,
666
- organisations: [],
667
- });
668
-
669
- renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={true} />);
670
-
671
- expect(screen.getByTestId('alert')).toBeInTheDocument();
672
- expect(screen.getByTestId('alert-description')).toHaveTextContent('No organisations available');
673
- });
674
-
675
- it('handles undefined onOrganisationChange callback', async () => {
676
- const user = userEvent.setup();
677
-
678
- renderWithProviders(<OrganisationSelector />);
679
-
680
- const selectItem = screen.getByTestId('select-item-org-2');
681
- await user.click(selectItem);
682
-
683
- // Should not throw error
684
- expect(screen.getByTestId('select')).toBeInTheDocument();
685
- });
686
-
687
- it('handles null selectedOrganisation gracefully', () => {
688
- mockUseOrganisations.mockReturnValue({
689
- ...defaultMockContext,
690
- selectedOrganisation: null,
691
- isLoading: true, // This should trigger loading state
692
- });
693
-
694
- // This should render the loading state instead of crashing
695
- renderWithProviders(<OrganisationSelector />);
696
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
697
- });
698
-
699
- it('handles very long organisation names', () => {
700
- const longNameOrg = {
701
- ...mockOrganisations[0],
702
- display_name: 'A'.repeat(1000),
703
- };
704
-
705
- mockUseOrganisations.mockReturnValue({
706
- ...defaultMockContext,
707
- organisations: [longNameOrg],
708
- });
709
-
710
- renderWithProviders(<OrganisationSelector />);
711
-
712
- expect(screen.getByText(longNameOrg.display_name)).toBeInTheDocument();
713
- });
714
- });
715
-
716
- // Performance tests
717
- describe('Performance', () => {
718
- it('renders quickly with many organisations', () => {
719
- const manyOrgs = Array.from({ length: 100 }, (_, i) => ({
720
- ...mockOrganisations[0],
721
- id: `org-${i}`,
722
- display_name: `Organisation ${i}`,
723
- }));
724
-
725
- mockUseOrganisations.mockReturnValue({
726
- ...defaultMockContext,
727
- organisations: manyOrgs,
728
- });
729
-
730
- const startTime = performance.now();
731
- renderWithProviders(<OrganisationSelector />);
732
- const endTime = performance.now();
733
-
734
- expect(endTime - startTime).toBeLessThan(500); // Should render in under 500ms
735
- });
736
-
737
- it('handles rapid organisation switches', async () => {
738
- const user = userEvent.setup();
739
-
740
- renderWithProviders(<OrganisationSelector />);
741
-
742
- // Rapid switches
743
- for (let i = 0; i < 5; i++) {
744
- const selectItem = screen.getByTestId(`select-item-org-${(i % 3) + 1}`);
745
- await user.click(selectItem);
746
- }
747
-
748
- expect(screen.getByTestId('select')).toBeInTheDocument();
749
- });
750
- });
751
-
752
- // Integration tests
753
- describe('Integration Scenarios', () => {
754
- it('works with all props enabled', () => {
755
- renderWithProviders(
756
- <OrganisationSelector
757
- placeholder="Choose organisation..."
758
- className="custom-class"
759
- showRole={true}
760
- compact={false}
761
- showRetryButton={true}
762
- showNoOrganisationsMessage={true}
763
- disabled={false}
764
- />
765
- );
766
-
767
- expect(screen.getByTestId('select')).toBeInTheDocument();
768
- expect(screen.getByText('Choose organisation...')).toBeInTheDocument();
769
- });
770
-
771
- it('works in minimal configuration', () => {
772
- renderWithProviders(
773
- <OrganisationSelector
774
- showRole={false}
775
- compact={true}
776
- showRetryButton={false}
777
- showNoOrganisationsMessage={false}
778
- />
779
- );
780
-
781
- expect(screen.getByTestId('select')).toBeInTheDocument();
782
- });
783
- });
784
- });