@jmruthers/pace-core 0.6.4 → 0.6.6

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 (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -1,783 +0,0 @@
1
- /**
2
- * @file RoleBasedRouter Component Tests
3
- * @package @jmruthers/pace-core
4
- * @module RBAC/Components/RoleBasedRouter
5
- * @since 2.0.0
6
- *
7
- * Comprehensive tests for the RoleBasedRouter component covering all critical functionality.
8
- */
9
-
10
- import { render, screen, waitFor } from '@testing-library/react';
11
- import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
12
- import { ReactNode } from 'react';
13
- import { MemoryRouter, Routes, Route } from 'react-router-dom';
14
- import { RoleBasedRouter, useRoleBasedRouter } from '../RoleBasedRouter';
15
- import { useCan } from '../../hooks';
16
- import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
17
-
18
- // Mock the RBAC hooks
19
- vi.mock('../../hooks', () => ({
20
- useCan: vi.fn()
21
- }));
22
-
23
- // Mock the auth provider
24
- const mockUseUnifiedAuthFn = vi.fn();
25
- vi.mock('../../../providers/services/UnifiedAuthProvider', () => ({
26
- useUnifiedAuth: () => mockUseUnifiedAuthFn(),
27
- UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
28
- }));
29
-
30
- // Mock useResolvedScope
31
- const mockUseResolvedScopeFn = vi.fn();
32
- vi.mock('../../hooks/useResolvedScope', () => ({
33
- useResolvedScope: () => mockUseResolvedScopeFn(),
34
- }));
35
-
36
- // Mock React Router
37
- vi.mock('react-router-dom', async () => {
38
- const actual = await vi.importActual('react-router-dom');
39
- return {
40
- ...actual,
41
- useLocation: vi.fn(),
42
- useNavigate: vi.fn(),
43
- Outlet: vi.fn()
44
- };
45
- });
46
-
47
- import { useLocation, useNavigate, Outlet } from 'react-router-dom';
48
-
49
- // Mock data
50
- const mockUser = {
51
- id: 'user-123',
52
- email: 'test@example.com'
53
- };
54
-
55
- const mockScope = {
56
- organisationId: 'org-123',
57
- eventId: 'event-123',
58
- appId: undefined // Most tests expect undefined
59
- };
60
-
61
- const mockRoutes = [
62
- {
63
- path: '/dashboard',
64
- component: () => <div data-testid="dashboard">Dashboard</div>,
65
- permissions: ['read:dashboard'] as const,
66
- pageId: 'dashboard'
67
- },
68
- {
69
- path: '/admin',
70
- component: () => <div data-testid="admin">Admin</div>,
71
- permissions: ['admin:system'] as const,
72
- pageId: 'admin',
73
- strictMode: true
74
- },
75
- {
76
- path: '/public',
77
- component: () => <div data-testid="public">Public</div>,
78
- permissions: [] as const
79
- }
80
- ];
81
-
82
- // Test components
83
- const TestComponent = ({ children }: { children: ReactNode }) => (
84
- <div data-testid="test-component">{children}</div>
85
- );
86
-
87
- const TestUnauthorized = ({ route, reason }: { route: string; reason: string }) => (
88
- <div data-testid="test-unauthorized">
89
- Unauthorized: {route} - {reason}
90
- </div>
91
- );
92
-
93
- const TestOutlet = () => <div data-testid="test-outlet">Outlet</div>;
94
-
95
- // Mock Outlet component
96
- vi.mocked(Outlet).mockImplementation(TestOutlet);
97
-
98
- describe('RoleBasedRouter Component', () => {
99
- const mockUseCan = vi.mocked(useCan);
100
- const mockUseLocation = vi.mocked(useLocation);
101
- const mockUseNavigate = vi.mocked(useNavigate);
102
-
103
- beforeEach(() => {
104
- vi.clearAllMocks();
105
-
106
- // Default mock implementations
107
- mockUseUnifiedAuthFn.mockReturnValue({
108
- user: mockUser,
109
- selectedOrganisation: { id: 'org-123' },
110
- selectedEvent: { event_id: 'event-123' },
111
- supabase: {} as any,
112
- });
113
-
114
- // Mock useResolvedScope to return resolved scope
115
- // Note: appId is undefined in some tests, so we'll set it conditionally
116
- mockUseResolvedScopeFn.mockReturnValue({
117
- resolvedScope: {
118
- organisationId: 'org-123',
119
- eventId: 'event-123',
120
- appId: undefined, // Default to undefined for most tests
121
- },
122
- isLoading: false,
123
- error: null,
124
- });
125
-
126
- mockUseLocation.mockReturnValue({
127
- pathname: '/dashboard',
128
- search: '',
129
- hash: '',
130
- state: null,
131
- key: 'test'
132
- });
133
-
134
- mockUseNavigate.mockReturnValue(vi.fn());
135
-
136
- mockUseCan.mockReturnValue({
137
- can: true,
138
- isLoading: false,
139
- error: null
140
- });
141
- });
142
-
143
- describe('Rendering', () => {
144
- it('renders children when user has permission', async () => {
145
- mockUseCan.mockReturnValue({
146
- can: true,
147
- isLoading: false,
148
- error: null
149
- });
150
-
151
- render(
152
- <MemoryRouter initialEntries={['/dashboard']}>
153
- <RoleBasedRouter routes={mockRoutes}>
154
- <TestComponent>App Content</TestComponent>
155
- </RoleBasedRouter>
156
- </MemoryRouter>
157
- );
158
-
159
- await waitFor(() => {
160
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
161
- expect(screen.getByText('App Content')).toBeInTheDocument();
162
- }, { interval: 10 });
163
- });
164
-
165
- it('renders unauthorized component when user lacks permission', async () => {
166
- mockUseCan.mockReturnValue({
167
- can: false,
168
- isLoading: false,
169
- error: null
170
- });
171
-
172
- render(
173
- <MemoryRouter initialEntries={['/admin']}>
174
- <RoleBasedRouter
175
- routes={mockRoutes}
176
- unauthorizedComponent={TestUnauthorized}
177
- >
178
- <TestComponent>App Content</TestComponent>
179
- </RoleBasedRouter>
180
- </MemoryRouter>
181
- );
182
-
183
- await waitFor(() => {
184
- expect(screen.getByTestId('test-unauthorized')).toBeInTheDocument();
185
- // Text is split across elements, so check for parts
186
- expect(screen.getByText(/Unauthorized:/)).toBeInTheDocument();
187
- expect(screen.getByText(/Insufficient permissions/)).toBeInTheDocument();
188
- }, { interval: 10 });
189
- });
190
-
191
- it('shows loading state while checking permissions', () => {
192
- mockUseCan.mockReturnValue({
193
- can: false,
194
- isLoading: true,
195
- error: null
196
- });
197
-
198
- render(
199
- <MemoryRouter initialEntries={['/dashboard']}>
200
- <RoleBasedRouter routes={mockRoutes}>
201
- <TestComponent>App Content</TestComponent>
202
- </RoleBasedRouter>
203
- </MemoryRouter>
204
- );
205
-
206
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
207
- expect(screen.queryByTestId('test-component')).not.toBeInTheDocument();
208
- });
209
-
210
- it('renders outlet for route components', async () => {
211
- mockUseCan.mockReturnValue({
212
- can: true,
213
- isLoading: false,
214
- error: null
215
- });
216
-
217
- render(
218
- <MemoryRouter initialEntries={['/dashboard']}>
219
- <RoleBasedRouter routes={mockRoutes}>
220
- <TestComponent>App Content</TestComponent>
221
- </RoleBasedRouter>
222
- </MemoryRouter>
223
- );
224
-
225
- await waitFor(() => {
226
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
227
- expect(screen.getByText('App Content')).toBeInTheDocument();
228
- }, { interval: 10 });
229
- });
230
- });
231
-
232
- describe('Route Protection', () => {
233
- it('protects routes based on permissions', async () => {
234
- mockUseCan.mockReturnValue({
235
- can: true,
236
- isLoading: false,
237
- error: null
238
- });
239
-
240
- render(
241
- <MemoryRouter initialEntries={['/dashboard']}>
242
- <RoleBasedRouter routes={mockRoutes}>
243
- <TestComponent>App Content</TestComponent>
244
- </RoleBasedRouter>
245
- </MemoryRouter>
246
- );
247
-
248
- await waitFor(() => {
249
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
250
- }, { interval: 10 });
251
-
252
- expect(mockUseCan).toHaveBeenCalledWith(
253
- 'user-123',
254
- expect.objectContaining({
255
- organisationId: 'org-123',
256
- eventId: 'event-123',
257
- }),
258
- 'read:dashboard',
259
- 'dashboard',
260
- true, // useCache
261
- null, // precomputedSuperAdmin
262
- undefined // appName
263
- );
264
- // Check that appId is either undefined or matches expected value
265
- const call = mockUseCan.mock.calls.find(c => c[0] === 'user-123' && c[2] === 'read:dashboard');
266
- expect(call).toBeDefined();
267
- if (call) {
268
- expect(call[1].appId).toBeUndefined();
269
- }
270
- });
271
-
272
- it('denies access to routes without permissions', async () => {
273
- mockUseCan.mockReturnValue({
274
- can: false,
275
- isLoading: false,
276
- error: null
277
- });
278
-
279
- render(
280
- <MemoryRouter initialEntries={['/public']}>
281
- <RoleBasedRouter routes={mockRoutes}>
282
- <TestComponent>App Content</TestComponent>
283
- </RoleBasedRouter>
284
- </MemoryRouter>
285
- );
286
-
287
- await waitFor(() => {
288
- expect(screen.getByText('Access Denied')).toBeInTheDocument();
289
- expect(screen.getByText('You don\'t have permission to access')).toBeInTheDocument();
290
- }, { interval: 10 });
291
- });
292
-
293
- it('handles routes not found in configuration', async () => {
294
- mockUseLocation.mockReturnValue({
295
- pathname: '/unknown',
296
- search: '',
297
- hash: '',
298
- state: null,
299
- key: 'test'
300
- });
301
-
302
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
303
-
304
- render(
305
- <MemoryRouter initialEntries={['/unknown']}>
306
- <RoleBasedRouter routes={mockRoutes} strictMode={true}>
307
- <TestComponent>App Content</TestComponent>
308
- </RoleBasedRouter>
309
- </MemoryRouter>
310
- );
311
-
312
- await waitFor(() => {
313
- expect(consoleSpy).toHaveBeenCalledWith(
314
- expect.stringContaining('STRICT MODE VIOLATION'),
315
- expect.objectContaining({
316
- route: '/unknown',
317
- userId: 'user-123'
318
- })
319
- );
320
- });
321
-
322
- consoleSpy.mockRestore();
323
- });
324
-
325
- it('redirects to fallback route when unauthorized', async () => {
326
- const mockNavigate = vi.fn();
327
- mockUseNavigate.mockReturnValue(mockNavigate);
328
-
329
- mockUseCan.mockReturnValue({
330
- can: false,
331
- isLoading: false,
332
- error: null
333
- });
334
-
335
- render(
336
- <MemoryRouter initialEntries={['/admin']}>
337
- <RoleBasedRouter
338
- routes={mockRoutes}
339
- fallbackRoute="/unauthorized"
340
- >
341
- <TestComponent>App Content</TestComponent>
342
- </RoleBasedRouter>
343
- </MemoryRouter>
344
- );
345
-
346
- await waitFor(() => {
347
- expect(mockNavigate).toHaveBeenCalledWith('/unauthorized', { replace: true });
348
- });
349
- });
350
- });
351
-
352
- describe('Context Provider', () => {
353
- it('provides router context to children', async () => {
354
- const TestConsumer = () => {
355
- const context = useRoleBasedRouter();
356
- return (
357
- <div data-testid="context-consumer">
358
- <div data-testid="strict-mode">{context.isStrictMode.toString()}</div>
359
- <div data-testid="audit-log">{context.isAuditLogEnabled.toString()}</div>
360
- </div>
361
- );
362
- };
363
-
364
- mockUseCan.mockReturnValue({
365
- can: true,
366
- isLoading: false,
367
- error: null
368
- });
369
-
370
- render(
371
- <MemoryRouter initialEntries={['/dashboard']}>
372
- <RoleBasedRouter routes={mockRoutes} strictMode={true} auditLog={false}>
373
- <TestConsumer />
374
- </RoleBasedRouter>
375
- </MemoryRouter>
376
- );
377
-
378
- await waitFor(() => {
379
- expect(screen.getByTestId('context-consumer')).toBeInTheDocument();
380
- expect(screen.getByTestId('strict-mode')).toHaveTextContent('true');
381
- expect(screen.getByTestId('audit-log')).toHaveTextContent('false');
382
- }, { interval: 10 });
383
- });
384
-
385
- it('throws error when useRoleBasedRouter is used outside provider', () => {
386
- const TestConsumer = () => {
387
- useRoleBasedRouter();
388
- return <div>Should not render</div>;
389
- };
390
-
391
- expect(() => {
392
- render(<TestConsumer />);
393
- }).toThrow('useRoleBasedRouter must be used within a RoleBasedRouter');
394
- });
395
- });
396
-
397
- describe('Route Access Management', () => {
398
- it('records route access attempts', async () => {
399
- const onRouteAccessSpy = vi.fn();
400
-
401
- mockUseCan.mockReturnValue({
402
- can: true,
403
- isLoading: false,
404
- error: null
405
- });
406
-
407
- render(
408
- <MemoryRouter initialEntries={['/dashboard']}>
409
- <RoleBasedRouter
410
- routes={mockRoutes}
411
- onRouteAccess={onRouteAccessSpy}
412
- auditLog={true}
413
- >
414
- <TestComponent>App Content</TestComponent>
415
- </RoleBasedRouter>
416
- </MemoryRouter>
417
- );
418
-
419
- await waitFor(() => {
420
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
421
- }, { interval: 10 });
422
-
423
- expect(onRouteAccessSpy).toHaveBeenCalledWith(
424
- '/dashboard',
425
- true,
426
- expect.objectContaining({
427
- route: '/dashboard',
428
- permissions: ['read:dashboard'],
429
- userId: 'user-123',
430
- allowed: true
431
- })
432
- );
433
- });
434
-
435
- it('handles strict mode violations', async () => {
436
- const onStrictModeViolationSpy = vi.fn();
437
-
438
- mockUseCan.mockReturnValue({
439
- can: false,
440
- isLoading: false,
441
- error: null
442
- });
443
-
444
- render(
445
- <MemoryRouter initialEntries={['/admin']}>
446
- <RoleBasedRouter
447
- routes={mockRoutes}
448
- onStrictModeViolation={onStrictModeViolationSpy}
449
- strictMode={true}
450
- >
451
- <TestComponent>App Content</TestComponent>
452
- </RoleBasedRouter>
453
- </MemoryRouter>
454
- );
455
-
456
- await waitFor(() => {
457
- expect(onStrictModeViolationSpy).toHaveBeenCalledWith(
458
- '/dashboard',
459
- expect.objectContaining({
460
- route: '/dashboard',
461
- permissions: ['read:dashboard'],
462
- userId: 'user-123',
463
- allowed: false
464
- })
465
- );
466
- });
467
- });
468
-
469
- it('manages route access history', async () => {
470
- mockUseCan.mockReturnValue({
471
- can: true,
472
- isLoading: false,
473
- error: null
474
- });
475
-
476
- const TestHistoryConsumer = () => {
477
- const context = useRoleBasedRouter();
478
- const history = context.getRouteAccessHistory();
479
- return (
480
- <div data-testid="history-length">{history.length}</div>
481
- );
482
- };
483
-
484
- render(
485
- <MemoryRouter initialEntries={['/dashboard']}>
486
- <RoleBasedRouter routes={mockRoutes}>
487
- <TestHistoryConsumer />
488
- </RoleBasedRouter>
489
- </MemoryRouter>
490
- );
491
-
492
- await waitFor(() => {
493
- expect(screen.getByTestId('history-length')).toHaveTextContent('1');
494
- }, { interval: 10 });
495
- });
496
-
497
- it('provides accessible routes', async () => {
498
- mockUseCan.mockReturnValue({
499
- can: true,
500
- isLoading: false,
501
- error: null
502
- });
503
-
504
- const TestAccessibleConsumer = () => {
505
- const context = useRoleBasedRouter();
506
- const accessibleRoutes = context.getAccessibleRoutes();
507
- return (
508
- <div data-testid="accessible-routes">{accessibleRoutes.length}</div>
509
- );
510
- };
511
-
512
- render(
513
- <MemoryRouter initialEntries={['/dashboard']}>
514
- <RoleBasedRouter routes={mockRoutes}>
515
- <TestAccessibleConsumer />
516
- </RoleBasedRouter>
517
- </MemoryRouter>
518
- );
519
-
520
- await waitFor(() => {
521
- expect(screen.getByTestId('accessible-routes')).toHaveTextContent('3');
522
- }, { interval: 10 });
523
- });
524
-
525
- it('provides route configuration', async () => {
526
- mockUseCan.mockReturnValue({
527
- can: true,
528
- isLoading: false,
529
- error: null
530
- });
531
-
532
- const TestConfigConsumer = () => {
533
- const context = useRoleBasedRouter();
534
- const routeConfig = context.getRouteConfig('/dashboard');
535
- return (
536
- <div data-testid="route-config">{routeConfig?.path}</div>
537
- );
538
- };
539
-
540
- render(
541
- <MemoryRouter initialEntries={['/dashboard']}>
542
- <RoleBasedRouter routes={mockRoutes}>
543
- <TestConfigConsumer />
544
- </RoleBasedRouter>
545
- </MemoryRouter>
546
- );
547
-
548
- await waitFor(() => {
549
- expect(screen.getByTestId('route-config')).toHaveTextContent('/dashboard');
550
- }, { interval: 10 });
551
- });
552
- });
553
-
554
- describe('Configuration Options', () => {
555
- it('respects strictMode setting', async () => {
556
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
557
-
558
- mockUseCan.mockReturnValue({
559
- can: false,
560
- isLoading: false,
561
- error: null
562
- });
563
-
564
- render(
565
- <MemoryRouter initialEntries={['/admin']}>
566
- <RoleBasedRouter routes={mockRoutes} strictMode={false}>
567
- <TestComponent>App Content</TestComponent>
568
- </RoleBasedRouter>
569
- </MemoryRouter>
570
- );
571
-
572
- await waitFor(() => {
573
- expect(screen.getByText('Access Denied')).toBeInTheDocument();
574
- }, { interval: 10 });
575
-
576
- expect(consoleSpy).not.toHaveBeenCalledWith(
577
- expect.stringContaining('STRICT MODE VIOLATION')
578
- );
579
-
580
- consoleSpy.mockRestore();
581
- });
582
-
583
- it('respects auditLog setting', async () => {
584
- const onRouteAccessSpy = vi.fn();
585
-
586
- mockUseCan.mockReturnValue({
587
- can: true,
588
- isLoading: false,
589
- error: null
590
- });
591
-
592
- render(
593
- <MemoryRouter initialEntries={['/dashboard']}>
594
- <RoleBasedRouter
595
- routes={mockRoutes}
596
- onRouteAccess={onRouteAccessSpy}
597
- auditLog={false}
598
- >
599
- <TestComponent>App Content</TestComponent>
600
- </RoleBasedRouter>
601
- </MemoryRouter>
602
- );
603
-
604
- await waitFor(() => {
605
- expect(screen.getByTestId('test-component')).toBeInTheDocument();
606
- }, { interval: 10 });
607
-
608
- expect(onRouteAccessSpy).not.toHaveBeenCalled();
609
- });
610
-
611
- it('respects maxHistorySize setting', async () => {
612
- mockUseCan.mockReturnValue({
613
- can: true,
614
- isLoading: false,
615
- error: null
616
- });
617
-
618
- const TestHistoryConsumer = () => {
619
- const context = useRoleBasedRouter();
620
- const history = context.getRouteAccessHistory();
621
- return (
622
- <div data-testid="history-length">{history.length}</div>
623
- );
624
- };
625
-
626
- render(
627
- <MemoryRouter initialEntries={['/dashboard']}>
628
- <RoleBasedRouter routes={mockRoutes} maxHistorySize={5}>
629
- <TestHistoryConsumer />
630
- </RoleBasedRouter>
631
- </MemoryRouter>
632
- );
633
-
634
- await waitFor(() => {
635
- expect(screen.getByTestId('history-length')).toHaveTextContent('1');
636
- }, { interval: 10 });
637
- });
638
- });
639
-
640
- describe('Error Handling', () => {
641
- it('handles missing user gracefully', async () => {
642
- mockUseUnifiedAuthFn.mockReturnValue({
643
- user: null,
644
- selectedOrganisation: { id: 'org-123' },
645
- selectedEvent: { event_id: 'event-123' }
646
- });
647
-
648
- mockUseCan.mockReturnValue({
649
- can: false,
650
- isLoading: false,
651
- error: null
652
- });
653
-
654
- render(
655
- <MemoryRouter initialEntries={['/dashboard']}>
656
- <RoleBasedRouter routes={mockRoutes}>
657
- <TestComponent>App Content</TestComponent>
658
- </RoleBasedRouter>
659
- </MemoryRouter>
660
- );
661
-
662
- await waitFor(() => {
663
- expect(screen.getByText('Access Denied')).toBeInTheDocument();
664
- }, { interval: 10 });
665
-
666
- expect(mockUseCan).toHaveBeenCalledWith(
667
- '',
668
- expect.objectContaining({
669
- organisationId: 'org-123',
670
- eventId: 'event-123',
671
- }),
672
- 'read:dashboard',
673
- 'dashboard',
674
- true, // useCache
675
- null, // precomputedSuperAdmin
676
- undefined // appName
677
- );
678
- // Check that appId is either undefined or matches expected value
679
- const call = mockUseCan.mock.calls.find(c => c[0] === '' && c[2] === 'read:dashboard');
680
- expect(call).toBeDefined();
681
- if (call) {
682
- expect(call[1].appId).toBeUndefined();
683
- }
684
- });
685
-
686
- it('handles missing organisation context', async () => {
687
- mockUseUnifiedAuthFn.mockReturnValue({
688
- user: mockUser,
689
- selectedOrganisation: null,
690
- selectedEvent: null
691
- });
692
-
693
- mockUseCan.mockReturnValue({
694
- can: false,
695
- isLoading: false,
696
- error: null
697
- });
698
-
699
- render(
700
- <MemoryRouter initialEntries={['/dashboard']}>
701
- <RoleBasedRouter routes={mockRoutes}>
702
- <TestComponent>App Content</TestComponent>
703
- </RoleBasedRouter>
704
- </MemoryRouter>
705
- );
706
-
707
- await waitFor(() => {
708
- expect(screen.getByText('Access Denied')).toBeInTheDocument();
709
- }, { interval: 10 });
710
- });
711
-
712
- it('handles permission check errors', async () => {
713
- const error = new Error('Permission check failed');
714
- mockUseCan.mockReturnValue({
715
- can: false,
716
- isLoading: false,
717
- error
718
- });
719
-
720
- render(
721
- <MemoryRouter initialEntries={['/dashboard']}>
722
- <RoleBasedRouter routes={mockRoutes}>
723
- <TestComponent>App Content</TestComponent>
724
- </RoleBasedRouter>
725
- </MemoryRouter>
726
- );
727
-
728
- await waitFor(() => {
729
- expect(screen.getByText('Access Denied')).toBeInTheDocument();
730
- }, { interval: 10 });
731
- });
732
- });
733
-
734
- describe('Route Changes', () => {
735
- it('handles route changes correctly', async () => {
736
- const { rerender } = render(
737
- <MemoryRouter initialEntries={['/dashboard']}>
738
- <RoleBasedRouter routes={mockRoutes}>
739
- <TestComponent>App Content</TestComponent>
740
- </RoleBasedRouter>
741
- </MemoryRouter>
742
- );
743
-
744
- // Change route
745
- mockUseLocation.mockReturnValue({
746
- pathname: '/admin',
747
- search: '',
748
- hash: '',
749
- state: null,
750
- key: 'test2'
751
- });
752
-
753
- rerender(
754
- <MemoryRouter initialEntries={['/admin']}>
755
- <RoleBasedRouter routes={mockRoutes}>
756
- <TestComponent>App Content</TestComponent>
757
- </RoleBasedRouter>
758
- </MemoryRouter>
759
- );
760
-
761
- await waitFor(() => {
762
- expect(mockUseCan).toHaveBeenCalledWith(
763
- 'user-123',
764
- expect.objectContaining({
765
- organisationId: 'org-123',
766
- eventId: 'event-123',
767
- }),
768
- 'admin:system',
769
- 'admin',
770
- true, // useCache
771
- null, // precomputedSuperAdmin
772
- undefined // appName
773
- );
774
- }, { interval: 10 });
775
- // Check that appId is either undefined or matches expected value
776
- const call = mockUseCan.mock.calls.find(c => c[0] === 'user-123' && c[2] === 'admin:system');
777
- expect(call).toBeDefined();
778
- if (call) {
779
- expect(call[1].appId).toBeUndefined();
780
- }
781
- });
782
- });
783
- });