@jmruthers/pace-core 0.5.54 → 0.5.56

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 (396) hide show
  1. package/README.md +0 -4
  2. package/dist/{DataTable-7FMFXA7A.js → DataTable-DJQTKX33.js} +11 -11
  3. package/dist/{PublicLoadingSpinner-Bq_-BeK-.d.ts → PublicLoadingSpinner-SL8WaQN7.d.ts} +2 -21
  4. package/dist/{api-H5A3H4IR.js → api-LUNF5O6M.js} +3 -3
  5. package/dist/{appConfig-BVGyuvI7.d.ts → appConfig-DjpeG6P-.d.ts} +9 -1
  6. package/dist/{appNameResolver-7GHF5ED2.js → appNameResolver-UURKN7NF.js} +2 -2
  7. package/dist/{audit-BUW3LMJB.js → audit-6TOCAMKO.js} +2 -2
  8. package/dist/{chunk-NRK4AIHQ.js → chunk-2DFCT6D3.js} +3 -3
  9. package/dist/{chunk-GIO7BFE7.js → chunk-3JKVVLD3.js} +66 -169
  10. package/dist/{chunk-GIO7BFE7.js.map → chunk-3JKVVLD3.js.map} +1 -1
  11. package/dist/{chunk-MZBUOP4P.js → chunk-5BSLGBYI.js} +4 -3
  12. package/dist/chunk-5BSLGBYI.js.map +1 -0
  13. package/dist/{chunk-I5Z3QH5X.js → chunk-66C4BSAY.js} +2 -2
  14. package/dist/{chunk-I5Z3QH5X.js.map → chunk-66C4BSAY.js.map} +1 -1
  15. package/dist/{chunk-EL2O4IUX.js → chunk-ASXSJGPW.js} +20 -24
  16. package/dist/{chunk-EL2O4IUX.js.map → chunk-ASXSJGPW.js.map} +1 -1
  17. package/dist/{chunk-7BNPOCLL.js → chunk-B2WTCLCV.js} +6 -2
  18. package/dist/chunk-B2WTCLCV.js.map +1 -0
  19. package/dist/{chunk-WJARTBCT.js → chunk-D7ARGIA3.js} +16 -7
  20. package/dist/chunk-D7ARGIA3.js.map +1 -0
  21. package/dist/{chunk-MYP2EGHX.js → chunk-GIDCWCHF.js} +21 -14
  22. package/dist/chunk-GIDCWCHF.js.map +1 -0
  23. package/dist/{chunk-MSFACPQQ.js → chunk-HYNGIE5T.js} +11 -11
  24. package/dist/{chunk-MSFACPQQ.js.map → chunk-HYNGIE5T.js.map} +1 -1
  25. package/dist/{chunk-TRIZ7IB7.js → chunk-I5GID3EX.js} +148 -288
  26. package/dist/chunk-I5GID3EX.js.map +1 -0
  27. package/dist/{chunk-GWSBHC4J.js → chunk-KLPVOPRI.js} +261 -38
  28. package/dist/chunk-KLPVOPRI.js.map +1 -0
  29. package/dist/{chunk-BC3S53OZ.js → chunk-N6XMGSGD.js} +30 -14
  30. package/dist/chunk-N6XMGSGD.js.map +1 -0
  31. package/dist/{chunk-6MTY77WU.js → chunk-QB4GXDUM.js} +3 -3
  32. package/dist/{chunk-YDJW5XTN.js → chunk-STT7INZR.js} +25 -1
  33. package/dist/chunk-STT7INZR.js.map +1 -0
  34. package/dist/{chunk-NYUJ4FJR.js → chunk-UETTVYKU.js} +7 -7
  35. package/dist/chunk-UETTVYKU.js.map +1 -0
  36. package/dist/{chunk-22KLBHPS.js → chunk-W66AZIOH.js} +2 -2
  37. package/dist/chunk-W66AZIOH.js.map +1 -0
  38. package/dist/{chunk-NZ655MWE.js → chunk-YEHO6FDW.js} +5 -4
  39. package/dist/chunk-YEHO6FDW.js.map +1 -0
  40. package/dist/{chunk-SS3E6QLB.js → chunk-YNUBMSMV.js} +2 -2
  41. package/dist/chunk-YNUBMSMV.js.map +1 -0
  42. package/dist/{chunk-74C6SNEC.js → chunk-ZPK5656W.js} +3 -3
  43. package/dist/{chunk-74C6SNEC.js.map → chunk-ZPK5656W.js.map} +1 -1
  44. package/dist/components.d.ts +22 -899
  45. package/dist/components.js +436 -3118
  46. package/dist/components.js.map +1 -1
  47. package/dist/file-reference-9xUOnwyt.d.ts +70 -0
  48. package/dist/hooks.d.ts +2 -2
  49. package/dist/hooks.js +10 -10
  50. package/dist/hooks.js.map +1 -1
  51. package/dist/index.d.ts +49 -9
  52. package/dist/index.js +190 -25
  53. package/dist/index.js.map +1 -1
  54. package/dist/{organisation-CO3Sh3_D.d.ts → organisation-t-vvQC3g.d.ts} +1 -8
  55. package/dist/providers.d.ts +2 -2
  56. package/dist/providers.js +5 -5
  57. package/dist/rbac/index.d.ts +65 -46
  58. package/dist/rbac/index.js +10 -12
  59. package/dist/styles/core.css +0 -125
  60. package/dist/types.d.ts +2 -1
  61. package/dist/types.js +3 -1
  62. package/dist/types.js.map +1 -1
  63. package/dist/{usePublicRouteParams-B2OcAsur.d.ts → usePublicRouteParams-CdoFxnJK.d.ts} +1 -1
  64. package/dist/utils.d.ts +3 -4
  65. package/dist/utils.js +44 -13
  66. package/dist/utils.js.map +1 -1
  67. package/docs/FILE_REFERENCE_SYSTEM.md +440 -0
  68. package/docs/INDEX.md +7 -5
  69. package/docs/README.md +0 -1
  70. package/docs/api/README.md +0 -4
  71. package/docs/api/classes/ErrorBoundary.md +1 -1
  72. package/docs/api/classes/InvalidScopeError.md +1 -1
  73. package/docs/api/classes/MissingUserContextError.md +1 -1
  74. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  75. package/docs/api/classes/PermissionDeniedError.md +2 -2
  76. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  77. package/docs/api/classes/RBACAuditManager.md +12 -12
  78. package/docs/api/classes/RBACCache.md +1 -1
  79. package/docs/api/classes/RBACEngine.md +6 -6
  80. package/docs/api/classes/RBACError.md +1 -1
  81. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  82. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  83. package/docs/api/classes/StorageUtils.md +281 -0
  84. package/docs/api/interfaces/AggregateConfig.md +1 -1
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CardProps.md +1 -1
  87. package/docs/api/interfaces/ColorPalette.md +1 -1
  88. package/docs/api/interfaces/ColorShade.md +1 -1
  89. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  90. package/docs/api/interfaces/DataTableAction.md +1 -1
  91. package/docs/api/interfaces/DataTableColumn.md +1 -1
  92. package/docs/api/interfaces/DataTableProps.md +1 -1
  93. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  94. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  95. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/EventContextType.md +1 -1
  97. package/docs/api/interfaces/EventLogoProps.md +1 -1
  98. package/docs/api/interfaces/EventProviderProps.md +1 -1
  99. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  107. package/docs/api/interfaces/NavigationContextType.md +1 -1
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACContextType.md +1 -1
  133. package/docs/api/interfaces/RBACLogger.md +1 -1
  134. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  135. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  136. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  137. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  138. package/docs/api/interfaces/RouteConfig.md +2 -2
  139. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  140. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  141. package/docs/api/interfaces/StorageConfig.md +1 -1
  142. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  143. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  144. package/docs/api/interfaces/StorageListOptions.md +1 -1
  145. package/docs/api/interfaces/StorageListResult.md +1 -1
  146. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  147. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  148. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  149. package/docs/api/interfaces/StyleImport.md +1 -1
  150. package/docs/api/interfaces/ToastActionElement.md +1 -1
  151. package/docs/api/interfaces/ToastProps.md +1 -1
  152. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  153. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  155. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  159. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  160. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  161. package/docs/api/interfaces/UserEventAccess.md +1 -1
  162. package/docs/api/interfaces/UserMenuProps.md +1 -1
  163. package/docs/api/interfaces/UserProfile.md +1 -1
  164. package/docs/api/modules.md +204 -200
  165. package/docs/api-reference/components.md +141 -163
  166. package/docs/api-reference/hooks.md +347 -0
  167. package/docs/core-concepts/rbac-system.md +69 -16
  168. package/docs/getting-started/examples/basic-auth-app.md +0 -1
  169. package/docs/implementation-guides/datatable-rbac-usage.md +12 -11
  170. package/docs/implementation-guides/file-upload-storage.md +733 -0
  171. package/docs/implementation-guides/inactivity-tracking.md +779 -0
  172. package/docs/implementation-guides/organisation-security.md +748 -0
  173. package/docs/implementation-guides/public-pages-advanced.md +1022 -0
  174. package/docs/migration/MIGRATION_GUIDE.md +684 -0
  175. package/docs/migration/README.md +13 -2
  176. package/docs/migration/rbac-migration.md +73 -0
  177. package/docs/rbac/examples/rbac-rls-integration-example.md +11 -13
  178. package/docs/style-guide.md +269 -1
  179. package/package.json +1 -1
  180. package/src/__tests__/TESTING_GUIDELINES.md +331 -18
  181. package/src/__tests__/helpers/supabaseMock.ts +99 -0
  182. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +10 -7
  183. package/src/__tests__/shared.ts +6 -0
  184. package/src/components/DataTable/components/ActionButtons.tsx +2 -2
  185. package/src/components/DataTable/components/DataTableCore.tsx +2 -2
  186. package/src/components/DataTable/components/UnifiedTableBody.tsx +1 -1
  187. package/src/components/DataTable/utils/debugTools.ts +2 -2
  188. package/src/components/Dialog/Dialog.test.tsx +12 -2
  189. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +6 -6
  190. package/src/components/ErrorBoundary/ErrorBoundary.tsx +2 -2
  191. package/src/components/FileDisplay.tsx +233 -0
  192. package/src/components/FileUpload.tsx +176 -0
  193. package/src/components/Footer/Footer.test.tsx +7 -7
  194. package/src/components/NavigationMenu/NavigationMenu.test.tsx +13 -6
  195. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +30 -3
  196. package/src/components/OrganisationSelector/OrganisationSelector.tsx +1 -1
  197. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +558 -0
  198. package/src/components/PublicLayout/PublicErrorBoundary.tsx +1 -1
  199. package/src/components/PublicLayout/PublicPageDebugger.tsx +2 -2
  200. package/src/components/PublicLayout/PublicPageDiagnostic.tsx +2 -2
  201. package/src/components/PublicLayout/PublicPageProvider.tsx +2 -2
  202. package/src/components/Select/Select.test.tsx +50 -15
  203. package/src/components/SuperAdminGuard.tsx +2 -2
  204. package/src/components/__tests__/SuperAdminGuard.test.tsx +559 -0
  205. package/src/components/index.ts +0 -183
  206. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +2 -2
  207. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +1 -1
  208. package/src/hooks/__tests__/useRBAC.unit.test.ts +191 -138
  209. package/src/hooks/public/usePublicEvent.ts +2 -2
  210. package/src/hooks/useAppConfig.ts +3 -3
  211. package/src/hooks/useComponentPerformance.ts +1 -1
  212. package/src/hooks/useDataTablePerformance.ts +1 -1
  213. package/src/hooks/useFileReference.ts +232 -0
  214. package/src/hooks/useOrganisationPermissions.test.ts +254 -344
  215. package/src/hooks/useOrganisationPermissions.ts +15 -7
  216. package/src/hooks/useOrganisationSecurity.test.ts +390 -402
  217. package/src/hooks/usePerformanceMonitor.ts +1 -1
  218. package/src/hooks/usePermissionCache.test.ts +264 -395
  219. package/src/hooks/usePermissionCache.ts +34 -4
  220. package/src/hooks/useSecureDataAccess.test.ts +486 -0
  221. package/src/hooks/useSecureDataAccess.ts +4 -1
  222. package/src/providers/InactivityProvider.tsx +2 -2
  223. package/src/providers/OrganisationProvider.test.simple.tsx +168 -0
  224. package/src/providers/OrganisationProvider.test.tsx +168 -0
  225. package/src/providers/OrganisationProvider.tsx +25 -31
  226. package/src/providers/UnifiedAuthProvider.test.simple.tsx +205 -0
  227. package/src/providers/UnifiedAuthProvider.test.tsx +128 -0
  228. package/src/providers/__tests__/InactivityProvider.test.tsx +3 -4
  229. package/src/providers/__tests__/OrganisationProvider.test.tsx +19 -14
  230. package/src/rbac/__tests__/integration.authflow.test.tsx +123 -0
  231. package/src/rbac/__tests__/integration.navigation.test.tsx +72 -0
  232. package/src/rbac/__tests__/integration.securedata.test.tsx +92 -0
  233. package/src/rbac/__tests__/integration.smoke.test.tsx +73 -0
  234. package/src/rbac/__tests__/rbac-core.test.tsx +26 -22
  235. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +411 -0
  236. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +285 -0
  237. package/src/rbac/__tests__/rbac-functions.test.ts +655 -0
  238. package/src/rbac/__tests__/rbac-integration.test.ts +532 -0
  239. package/src/rbac/__tests__/scenarios.user-role.test.tsx +196 -0
  240. package/src/rbac/api.test.ts +6 -6
  241. package/src/rbac/api.ts +2 -2
  242. package/src/rbac/audit.test.ts +485 -0
  243. package/src/rbac/audit.ts +7 -1
  244. package/src/rbac/cache-invalidation.ts +318 -0
  245. package/src/rbac/cache.test.ts +286 -0
  246. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +559 -0
  247. package/src/rbac/components/EnhancedNavigationMenu.tsx +29 -23
  248. package/src/rbac/components/NavigationProvider.test.tsx +449 -0
  249. package/src/rbac/components/PagePermissionGuard.tsx +4 -4
  250. package/src/rbac/components/PagePermissionProvider.test.tsx +479 -0
  251. package/src/rbac/components/SecureDataProvider.test.tsx +511 -0
  252. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +159 -430
  253. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +4 -5
  254. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +112 -118
  255. package/src/rbac/config.test.ts +410 -0
  256. package/src/rbac/engine.test.simple.ts +237 -0
  257. package/src/rbac/engine.test.ts +233 -0
  258. package/src/rbac/engine.ts +37 -41
  259. package/src/rbac/examples/CompleteRBACExample.tsx +3 -3
  260. package/src/rbac/examples/EventBasedApp.tsx +4 -4
  261. package/src/rbac/hooks/useRBAC.simple.test.ts +16 -0
  262. package/src/rbac/hooks/useRBAC.test.ts +207 -455
  263. package/src/rbac/hooks/useRBAC.ts +30 -22
  264. package/src/rbac/permissions.test.ts +128 -0
  265. package/src/rbac/permissions.ts +56 -141
  266. package/src/rbac/providers/RBACProvider.tsx +1 -1
  267. package/src/rbac/secureClient.test.ts +444 -0
  268. package/src/rbac/security.test.ts +390 -0
  269. package/src/rbac/security.ts +1 -1
  270. package/src/rbac/types.test.ts +382 -0
  271. package/src/rbac/types.ts +2 -2
  272. package/src/styles/core.css +0 -125
  273. package/src/types/file-reference.ts +77 -0
  274. package/src/types/rbac-functions.ts +290 -0
  275. package/src/types/supabase.ts +10 -28
  276. package/src/types/unified.ts +4 -1
  277. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +81 -55
  278. package/src/utils/__tests__/lazyLoad.unit.test.tsx +21 -12
  279. package/src/utils/__tests__/organisationContext.unit.test.ts +13 -7
  280. package/src/utils/__tests__/performanceBudgets.unit.test.ts +3 -3
  281. package/src/utils/__tests__/sessionTracking.unit.test.ts +32 -12
  282. package/src/utils/appConfig.ts +1 -1
  283. package/src/utils/appIdResolver.test.ts +503 -0
  284. package/src/utils/appIdResolver.ts +1 -1
  285. package/src/utils/appNameResolver.test.ts +494 -0
  286. package/src/utils/appNameResolver.ts +3 -2
  287. package/src/utils/bundleAnalysis.ts +3 -3
  288. package/src/utils/debugLogger.ts +1 -1
  289. package/src/utils/file-reference.ts +263 -0
  290. package/src/utils/formatDate.test.ts +2 -2
  291. package/src/utils/organisationContext.test.ts +340 -0
  292. package/src/utils/organisationContext.ts +19 -6
  293. package/src/utils/performanceBudgets.ts +2 -2
  294. package/src/utils/permissionUtils.test.ts +393 -0
  295. package/src/utils/permissionUtils.ts +5 -2
  296. package/src/utils/secureDataAccess.test.ts +715 -0
  297. package/src/utils/secureDataAccess.ts +21 -5
  298. package/src/utils/sessionTracking.ts +34 -4
  299. package/src/utils/storage/__tests__/helpers.unit.test.ts +328 -0
  300. package/src/utils/storage/__tests__/index.unit.test.ts +16 -0
  301. package/src/utils/storage/helpers.ts +20 -25
  302. package/src/utils/storage/index.ts +29 -1
  303. package/src/vite-env.d.ts +17 -0
  304. package/dist/chunk-22KLBHPS.js.map +0 -1
  305. package/dist/chunk-7BNPOCLL.js.map +0 -1
  306. package/dist/chunk-BC3S53OZ.js.map +0 -1
  307. package/dist/chunk-GWSBHC4J.js.map +0 -1
  308. package/dist/chunk-MYP2EGHX.js.map +0 -1
  309. package/dist/chunk-MZBUOP4P.js.map +0 -1
  310. package/dist/chunk-NYUJ4FJR.js.map +0 -1
  311. package/dist/chunk-NZ655MWE.js.map +0 -1
  312. package/dist/chunk-SS3E6QLB.js.map +0 -1
  313. package/dist/chunk-TRIZ7IB7.js.map +0 -1
  314. package/dist/chunk-WJARTBCT.js.map +0 -1
  315. package/dist/chunk-YDJW5XTN.js.map +0 -1
  316. package/docs/print-components/README.md +0 -258
  317. package/docs/print-components/api-reference.md +0 -636
  318. package/docs/print-components/examples/README.md +0 -204
  319. package/docs/print-components/examples/basic-report.tsx +0 -92
  320. package/docs/print-components/examples/card-catalog.tsx +0 -149
  321. package/docs/print-components/examples/cover-page-report.tsx +0 -163
  322. package/docs/print-components/quick-start.md +0 -363
  323. package/src/components/PrintButton/PrintButton.tsx +0 -321
  324. package/src/components/PrintButton/PrintButtonGroup.tsx +0 -84
  325. package/src/components/PrintButton/PrintToolbar.tsx +0 -94
  326. package/src/components/PrintButton/__tests__/PrintButton.test.tsx +0 -271
  327. package/src/components/PrintButton/examples/PrintButtonShowcase.tsx +0 -438
  328. package/src/components/PrintButton/index.ts +0 -33
  329. package/src/components/PrintButton/types.ts +0 -173
  330. package/src/components/PrintCard/PrintCard.tsx +0 -154
  331. package/src/components/PrintCard/PrintCardContent.tsx +0 -57
  332. package/src/components/PrintCard/PrintCardFooter.tsx +0 -60
  333. package/src/components/PrintCard/PrintCardGrid.tsx +0 -91
  334. package/src/components/PrintCard/PrintCardHeader.tsx +0 -78
  335. package/src/components/PrintCard/PrintCardImage.tsx +0 -81
  336. package/src/components/PrintCard/examples/PrintCardShowcase.tsx +0 -239
  337. package/src/components/PrintCard/index.ts +0 -34
  338. package/src/components/PrintCard/types.ts +0 -171
  339. package/src/components/PrintDataTable/PrintDataTable.tsx +0 -215
  340. package/src/components/PrintDataTable/PrintTableGroup.tsx +0 -90
  341. package/src/components/PrintDataTable/PrintTableRow.tsx +0 -76
  342. package/src/components/PrintDataTable/index.ts +0 -25
  343. package/src/components/PrintDataTable/types.ts +0 -67
  344. package/src/components/PrintFooter/PrintFooter.tsx +0 -183
  345. package/src/components/PrintFooter/PrintFooterContent.tsx +0 -71
  346. package/src/components/PrintFooter/PrintFooterInfo.tsx +0 -86
  347. package/src/components/PrintFooter/PrintPageNumber.tsx +0 -90
  348. package/src/components/PrintFooter/examples/PrintFooterShowcase.tsx +0 -390
  349. package/src/components/PrintFooter/index.ts +0 -30
  350. package/src/components/PrintFooter/types.ts +0 -149
  351. package/src/components/PrintGrid/PrintGrid.tsx +0 -180
  352. package/src/components/PrintGrid/PrintGridBreakpoint.tsx +0 -109
  353. package/src/components/PrintGrid/PrintGridContainer.tsx +0 -128
  354. package/src/components/PrintGrid/PrintGridItem.tsx +0 -220
  355. package/src/components/PrintGrid/examples/PrintGridShowcase.tsx +0 -359
  356. package/src/components/PrintGrid/index.ts +0 -31
  357. package/src/components/PrintGrid/types.ts +0 -159
  358. package/src/components/PrintHeader/PrintCoverHeader.tsx +0 -230
  359. package/src/components/PrintHeader/PrintHeader.tsx +0 -150
  360. package/src/components/PrintHeader/index.ts +0 -17
  361. package/src/components/PrintHeader/types.ts +0 -42
  362. package/src/components/PrintLayout/PrintLayout.tsx +0 -122
  363. package/src/components/PrintLayout/PrintLayoutContext.tsx +0 -66
  364. package/src/components/PrintLayout/PrintPageBreak.tsx +0 -52
  365. package/src/components/PrintLayout/examples/PrintShowcase.tsx +0 -230
  366. package/src/components/PrintLayout/index.ts +0 -19
  367. package/src/components/PrintLayout/types.ts +0 -37
  368. package/src/components/PrintPageBreak/PrintPageBreak.tsx +0 -120
  369. package/src/components/PrintPageBreak/PrintPageBreakGroup.tsx +0 -90
  370. package/src/components/PrintPageBreak/PrintPageBreakIndicator.tsx +0 -112
  371. package/src/components/PrintPageBreak/examples/PrintPageBreakShowcase.tsx +0 -279
  372. package/src/components/PrintPageBreak/index.ts +0 -23
  373. package/src/components/PrintPageBreak/types.ts +0 -94
  374. package/src/components/PrintSection/PrintColumn.tsx +0 -104
  375. package/src/components/PrintSection/PrintDivider.tsx +0 -101
  376. package/src/components/PrintSection/PrintSection.tsx +0 -129
  377. package/src/components/PrintSection/PrintSectionContent.tsx +0 -75
  378. package/src/components/PrintSection/PrintSectionHeader.tsx +0 -97
  379. package/src/components/PrintSection/examples/PrintSectionShowcase.tsx +0 -258
  380. package/src/components/PrintSection/index.ts +0 -33
  381. package/src/components/PrintSection/types.ts +0 -155
  382. package/src/components/PrintText/PrintText.tsx +0 -116
  383. package/src/components/PrintText/index.ts +0 -16
  384. package/src/components/PrintText/types.ts +0 -24
  385. package/src/rbac/__tests__/integration.test.tsx +0 -218
  386. package/src/utils/print/PrintDataProcessor.ts +0 -390
  387. package/src/utils/print/examples/PrintUtilitiesShowcase.tsx +0 -397
  388. package/src/utils/print/index.ts +0 -29
  389. package/src/utils/print/types.ts +0 -196
  390. package/src/utils/print/usePrintOptimization.ts +0 -272
  391. /package/dist/{DataTable-7FMFXA7A.js.map → DataTable-DJQTKX33.js.map} +0 -0
  392. /package/dist/{api-H5A3H4IR.js.map → api-LUNF5O6M.js.map} +0 -0
  393. /package/dist/{appNameResolver-7GHF5ED2.js.map → appNameResolver-UURKN7NF.js.map} +0 -0
  394. /package/dist/{audit-BUW3LMJB.js.map → audit-6TOCAMKO.js.map} +0 -0
  395. /package/dist/{chunk-NRK4AIHQ.js.map → chunk-2DFCT6D3.js.map} +0 -0
  396. /package/dist/{chunk-6MTY77WU.js.map → chunk-QB4GXDUM.js.map} +0 -0
@@ -1,478 +1,207 @@
1
1
  /**
2
- * @file PagePermissionGuard Race Condition Tests
2
+ * @file PagePermissionGuard Simplified Tests
3
3
  * @package @jmruthers/pace-core
4
- * @module RBAC/Components/PagePermissionGuard/RaceConditionTests
4
+ * @module RBAC/Components/PagePermissionGuard
5
5
  * @since 2.0.0
6
6
  *
7
- * Comprehensive tests to verify that PagePermissionGuard properly handles
8
- * race conditions and UI state transitions without infinite loops.
7
+ * Simplified tests for PagePermissionGuard component.
9
8
  */
10
9
 
11
10
  import React from 'react';
12
- import { render, screen, waitFor, act } from '@testing-library/react';
13
- import { vi } from 'vitest';
11
+ import { render, screen } from '@testing-library/react';
12
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
14
13
  import { PagePermissionGuard } from '../PagePermissionGuard';
15
- import { useUnifiedAuth } from '../../../providers/UnifiedAuthProvider';
16
- import { useCan } from '../../hooks/usePermissions';
17
-
18
- // Mock the hooks
19
- vi.mock('../../../providers/UnifiedAuthProvider');
20
- vi.mock('../../hooks/usePermissions');
21
-
22
- const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
23
- const mockUseCan = vi.mocked(useCan);
24
-
25
- // Mock console.log to track render cycles
26
- const originalConsoleLog = console.log;
27
- let renderCount = 0;
28
- let consoleLogs: string[] = [];
29
-
30
- beforeEach(() => {
31
- renderCount = 0;
32
- consoleLogs = [];
33
- console.log = (...args: any[]) => {
34
- consoleLogs.push(args.join(' '));
35
- originalConsoleLog(...args);
36
- };
37
- });
38
-
39
- afterEach(() => {
40
- console.log = originalConsoleLog;
41
- });
42
-
43
- describe('PagePermissionGuard Race Condition Tests', () => {
44
- const mockUser = { id: 'user-123', email: 'test@example.com' };
45
- const mockScope = {
46
- organisationId: 'org-123',
47
- eventId: 'event-123',
48
- appId: 'app-123'
49
- };
14
+ import { useCan } from '../../hooks';
15
+
16
+ // Mock the hooks with simple implementations
17
+ vi.mock('../../../providers/UnifiedAuthProvider', () => ({
18
+ useUnifiedAuth: () => ({
19
+ user: { id: 'user-123', email: 'test@example.com' },
20
+ isAuthenticated: true,
21
+ isLoading: false,
22
+ selectedOrganisationId: 'org-123',
23
+ selectedEventId: undefined,
24
+ supabase: {
25
+ from: vi.fn(() => ({
26
+ select: vi.fn(() => ({
27
+ eq: vi.fn(() => ({
28
+ single: vi.fn(() => Promise.resolve({ data: { id: 'app-123', name: 'test-app', is_active: true }, error: null }))
29
+ }))
30
+ }))
31
+ }))
32
+ }
33
+ })
34
+ }));
35
+
36
+ vi.mock('../../hooks', () => ({
37
+ useCan: vi.fn()
38
+ }));
39
+
40
+ // Mock the app name resolver
41
+ vi.mock('../../../utils/appNameResolver', () => ({
42
+ getCurrentAppName: () => 'test-app'
43
+ }));
44
+
45
+ describe('PagePermissionGuard Simplified Tests', () => {
46
+ const mockUseCan = vi.mocked(useCan);
50
47
 
51
48
  beforeEach(() => {
52
- // Reset mocks
53
49
  vi.clearAllMocks();
54
50
 
55
- // Default mock implementations
56
- mockUseUnifiedAuth.mockReturnValue({
57
- user: mockUser,
58
- selectedOrganisationId: 'org-123',
59
- selectedEventId: 'event-123',
60
- supabase: {} as any,
61
- isAuthenticated: true,
51
+ // Set up default mock behavior
52
+ mockUseCan.mockReturnValue({
53
+ can: false,
62
54
  isLoading: false,
63
55
  error: null,
64
- signOut: vi.fn(),
65
- refreshSession: vi.fn(),
66
- hasPermission: vi.fn(),
67
- hasRole: vi.fn(),
68
- hasAccessLevel: vi.fn(),
69
- validatePermission: vi.fn(),
70
- canAccess: vi.fn(),
71
- setSelectedEventId: vi.fn(),
72
- appName: 'test-app',
73
- hasErrors: false
74
56
  });
75
57
  });
76
58
 
77
- describe('Race Condition: Initial Loading to Permission Granted', () => {
78
- it('should handle the exact production scenario: loading → permission granted', async () => {
79
- // Simulate the exact scenario from the user's logs
80
- let canValue = false;
81
- let isLoadingValue = true;
82
- let callCount = 0;
83
-
84
- mockUseCan.mockImplementation(() => {
85
- callCount++;
86
- console.log(`[TEST] useCan call #${callCount}: can=${canValue}, isLoading=${isLoadingValue}`);
87
-
88
- // Simulate the race condition: first call returns false, second call returns true
89
- if (callCount === 1) {
90
- canValue = false;
91
- isLoadingValue = false;
92
- } else if (callCount === 2) {
93
- canValue = true;
94
- isLoadingValue = false;
95
- }
96
-
97
- return {
98
- can: canValue,
99
- isLoading: isLoadingValue,
100
- error: null
101
- };
59
+ describe('Basic Rendering', () => {
60
+ it('renders children when permission is granted', () => {
61
+ mockUseCan.mockReturnValue({
62
+ can: true,
63
+ isLoading: false,
64
+ error: null,
102
65
  });
103
-
104
- const TestComponent = () => {
105
- renderCount++;
106
- console.log(`[TEST] Component render #${renderCount}`);
107
-
108
- return (
109
- <PagePermissionGuard pageName="meals" operation="read">
110
- <div data-testid="protected-content">Protected Content</div>
111
- </PagePermissionGuard>
112
- );
113
- };
114
-
115
- const { rerender } = render(<TestComponent />);
116
-
117
- // Initial render should show loading (or content if scope resolves immediately)
118
- // Note: In test environment, scope resolution may be synchronous
119
- const hasLoading = screen.queryByText('Checking permissions...');
120
- const hasContent = screen.queryByTestId('protected-content');
121
66
 
122
- // Either loading or content should be visible, but not both
123
- expect(hasLoading || hasContent).toBeTruthy();
67
+ render(
68
+ <PagePermissionGuard
69
+ pageName="users"
70
+ operation="read"
71
+ scope={{ organisationId: 'org-123' }}
72
+ >
73
+ <div data-testid="protected-content">Protected Content</div>
74
+ </PagePermissionGuard>
75
+ );
124
76
 
125
- // Wait for content to appear (mock may not be applied in test environment)
126
- await waitFor(() => {
127
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
128
- });
129
-
130
- // Simulate the second permission check (this is where the race condition occurred)
131
- act(() => {
132
- // Trigger a re-render to simulate the second useCan call
133
- rerender(<TestComponent />);
134
- });
135
-
136
- // Wait for content to be stable (mock may not be applied in test environment)
137
- await waitFor(() => {
138
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
139
- });
140
-
141
- // Now the content should be visible
142
- await waitFor(() => {
143
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
144
- expect(screen.queryByText('Checking permissions...')).not.toBeInTheDocument();
145
- expect(screen.queryByText('Access Denied')).not.toBeInTheDocument();
146
- });
147
-
148
- // Verify we didn't have infinite loops
149
- expect(callCount).toBeLessThanOrEqual(3); // Should be exactly 2 calls, but allow some tolerance
150
- expect(renderCount).toBeLessThanOrEqual(5); // Should be reasonable number of renders
151
-
152
- // Verify that the component rendered correctly without infinite loops
153
- // The main goal is to ensure no infinite loops and proper final state
77
+ expect(screen.getByTestId('protected-content')).toBeInTheDocument();
78
+ expect(screen.getByText('Protected Content')).toBeInTheDocument();
154
79
  });
155
80
 
156
- it('should handle rapid state changes without infinite loops', async () => {
157
- let canValue = false;
158
- let isLoadingValue = true;
159
- let callCount = 0;
160
-
161
- mockUseCan.mockImplementation(() => {
162
- callCount++;
163
-
164
- // Simulate rapid state changes
165
- if (callCount <= 2) {
166
- canValue = false;
167
- isLoadingValue = true;
168
- } else if (callCount <= 4) {
169
- canValue = false;
170
- isLoadingValue = false;
171
- } else {
172
- canValue = true;
173
- isLoadingValue = false;
174
- }
175
-
176
- return {
177
- can: canValue,
178
- isLoading: isLoadingValue,
179
- error: null
180
- };
181
- });
182
-
183
- const TestComponent = () => {
184
- renderCount++;
185
- return (
186
- <PagePermissionGuard pageName="meals" operation="read">
187
- <div data-testid="protected-content">Protected Content</div>
188
- </PagePermissionGuard>
189
- );
190
- };
191
-
192
- const { rerender } = render(<TestComponent />);
193
-
194
- // Simulate multiple rapid re-renders
195
- for (let i = 0; i < 5; i++) {
196
- act(() => {
197
- rerender(<TestComponent />);
198
- });
199
- await new Promise(resolve => setTimeout(resolve, 10)); // Small delay
200
- }
201
-
202
- // Wait for final state
203
- await waitFor(() => {
204
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
205
- });
206
-
207
- // Verify no infinite loops
208
- expect(callCount).toBeLessThanOrEqual(6); // Should be reasonable
209
- expect(renderCount).toBeLessThanOrEqual(10); // Should be reasonable
81
+ it('renders access denied when permission is not granted', () => {
82
+ mockUseCan.mockReturnValue({
83
+ can: false,
84
+ isLoading: false,
85
+ error: null,
86
+ });
87
+
88
+ render(
89
+ <PagePermissionGuard
90
+ pageName="users"
91
+ operation="delete"
92
+ scope={{ organisationId: 'org-123' }}
93
+ >
94
+ <div data-testid="protected-content">Protected Content</div>
95
+ </PagePermissionGuard>
96
+ );
97
+
98
+ expect(screen.getByText('Access Denied')).toBeInTheDocument();
99
+ expect(screen.queryByTestId('protected-content')).not.toBeInTheDocument();
210
100
  });
211
101
 
212
- it('should handle scope resolution race conditions', async () => {
213
- let scopeResolved = false;
214
- let callCount = 0;
215
-
216
- // Mock scope resolution that takes time
217
- mockUseUnifiedAuth.mockImplementation(() => {
218
- if (!scopeResolved) {
219
- // Simulate scope not yet resolved
220
- return {
221
- user: mockUser,
222
- selectedOrganisationId: null,
223
- selectedEventId: null,
224
- supabase: {} as any,
225
- isAuthenticated: true,
226
- isLoading: true,
227
- error: null,
228
- signOut: vi.fn(),
229
- refreshSession: vi.fn(),
230
- hasPermission: vi.fn(),
231
- hasRole: vi.fn(),
232
- hasAccessLevel: vi.fn(),
233
- validatePermission: vi.fn(),
234
- canAccess: vi.fn(),
235
- setSelectedEventId: vi.fn(),
236
- appName: 'test-app',
237
- hasErrors: false
238
- };
239
- } else {
240
- // Scope is resolved
241
- return {
242
- user: mockUser,
243
- selectedOrganisationId: 'org-123',
244
- selectedEventId: 'event-123',
245
- supabase: {} as any,
246
- isAuthenticated: true,
247
- isLoading: false,
248
- error: null,
249
- signOut: vi.fn(),
250
- refreshSession: vi.fn(),
251
- hasPermission: vi.fn(),
252
- hasRole: vi.fn(),
253
- hasAccessLevel: vi.fn(),
254
- validatePermission: vi.fn(),
255
- canAccess: vi.fn(),
256
- setSelectedEventId: vi.fn(),
257
- appName: 'test-app',
258
- hasErrors: false
259
- };
260
- }
102
+ it('renders loading state when checking permissions', () => {
103
+ mockUseCan.mockReturnValue({
104
+ can: false,
105
+ isLoading: true,
106
+ error: null,
261
107
  });
262
108
 
263
- mockUseCan.mockImplementation(() => {
264
- callCount++;
265
- return {
266
- can: scopeResolved,
267
- isLoading: !scopeResolved,
268
- error: null
269
- };
270
- });
271
-
272
- const TestComponent = () => {
273
- renderCount++;
274
- return (
275
- <PagePermissionGuard pageName="meals" operation="read">
276
- <div data-testid="protected-content">Protected Content</div>
277
- </PagePermissionGuard>
278
- );
279
- };
109
+ render(
110
+ <PagePermissionGuard
111
+ pageName="users"
112
+ operation="read"
113
+ scope={{ organisationId: 'org-123' }}
114
+ >
115
+ <div data-testid="protected-content">Protected Content</div>
116
+ </PagePermissionGuard>
117
+ );
280
118
 
281
- const { rerender } = render(<TestComponent />);
282
-
283
- // Initial state: scope not resolved
119
+ // Should show loading state
284
120
  expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
285
-
286
- // Simulate scope resolution
287
- act(() => {
288
- scopeResolved = true;
289
- rerender(<TestComponent />);
290
- });
291
-
292
- // Wait for content to appear
293
- await waitFor(() => {
294
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
295
- });
296
-
297
- // Verify no infinite loops
298
- expect(callCount).toBeLessThanOrEqual(3);
299
- expect(renderCount).toBeLessThanOrEqual(5);
121
+ expect(screen.queryByTestId('protected-content')).not.toBeInTheDocument();
300
122
  });
301
123
  });
302
124
 
303
- describe('UI State Transition Verification', () => {
304
- it('should properly transition through all UI states', async () => {
305
- const stateSequence = [
306
- { can: false, isLoading: true, expectedUI: 'loading' },
307
- { can: false, isLoading: false, expectedUI: 'denied' },
308
- { can: true, isLoading: false, expectedUI: 'content' }
309
- ];
310
-
311
- let currentStateIndex = 0;
312
-
313
- mockUseCan.mockImplementation(() => {
314
- const currentState = stateSequence[currentStateIndex] || stateSequence[stateSequence.length - 1];
315
- return {
316
- can: currentState.can,
317
- isLoading: currentState.isLoading,
318
- error: null
319
- };
320
- });
321
-
322
- const TestComponent = () => {
323
- renderCount++;
324
- return (
325
- <PagePermissionGuard pageName="meals" operation="read">
326
- <div data-testid="protected-content">Protected Content</div>
327
- </PagePermissionGuard>
328
- );
329
- };
330
-
331
- const { rerender } = render(<TestComponent />);
332
-
333
- // Test each state transition
334
- for (let i = 0; i < stateSequence.length; i++) {
335
- act(() => {
336
- currentStateIndex = i;
337
- rerender(<TestComponent />);
338
- });
339
-
340
- const expectedState = stateSequence[i];
341
-
342
- if (expectedState.expectedUI === 'loading') {
343
- // In test environment, scope resolution may be synchronous
344
- const hasLoading = screen.queryByText('Checking permissions...');
345
- const hasContent = screen.queryByTestId('protected-content');
346
- expect(hasLoading || hasContent).toBeTruthy();
347
- } else if (expectedState.expectedUI === 'denied') {
348
- // In test environment, mock may not be applied, so check for content instead
349
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
350
- } else if (expectedState.expectedUI === 'content') {
351
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
352
- }
353
- }
354
-
355
- // Verify final state is correct
356
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
357
- expect(screen.queryByText('Loading organisation context...')).not.toBeInTheDocument();
358
- expect(screen.queryByText('Access Denied')).not.toBeInTheDocument();
125
+ describe('Error Handling', () => {
126
+ it('handles permission check errors gracefully', () => {
127
+ mockUseCan.mockReturnValue({
128
+ can: false,
129
+ isLoading: false,
130
+ error: new Error('Permission check failed'),
131
+ });
132
+
133
+ render(
134
+ <PagePermissionGuard
135
+ pageName="users"
136
+ operation="read"
137
+ scope={{ organisationId: 'org-123' }}
138
+ >
139
+ <div data-testid="protected-content">Protected Content</div>
140
+ </PagePermissionGuard>
141
+ );
142
+
143
+ expect(screen.getByText('Access Denied')).toBeInTheDocument();
144
+ expect(screen.queryByTestId('protected-content')).not.toBeInTheDocument();
359
145
  });
360
146
  });
361
147
 
362
- describe('Performance and Stability Tests', () => {
363
- it('should not create infinite loops with changing supabase reference', async () => {
364
- let supabaseRef = { id: 1 };
365
- let callCount = 0;
366
-
367
- mockUseUnifiedAuth.mockImplementation(() => {
368
- // Simulate supabase reference changing on every render
369
- supabaseRef = { id: Math.random() };
370
-
371
- return {
372
- user: mockUser,
373
- selectedOrganisationId: 'org-123',
374
- selectedEventId: 'event-123',
375
- supabase: supabaseRef as any,
376
- isAuthenticated: true,
377
- isLoading: false,
378
- error: null,
379
- signOut: vi.fn(),
380
- refreshSession: vi.fn(),
381
- hasPermission: vi.fn(),
382
- hasRole: vi.fn(),
383
- hasAccessLevel: vi.fn(),
384
- validatePermission: vi.fn(),
385
- canAccess: vi.fn(),
386
- setSelectedEventId: vi.fn(),
387
- appName: 'test-app',
388
- hasErrors: false
389
- };
390
- });
391
-
392
- mockUseCan.mockImplementation(() => {
393
- callCount++;
394
- return {
395
- can: true,
396
- isLoading: false,
397
- error: null
398
- };
148
+ describe('Props Validation', () => {
149
+ it('handles different permission formats', () => {
150
+ mockUseCan.mockReturnValue({
151
+ can: true,
152
+ isLoading: false,
153
+ error: null,
399
154
  });
400
-
401
- const TestComponent = () => {
402
- renderCount++;
403
- return (
404
- <PagePermissionGuard pageName="meals" operation="read">
405
- <div data-testid="protected-content">Protected Content</div>
155
+
156
+ const testCases = [
157
+ { pageName: 'users', operation: 'read' as const },
158
+ { pageName: 'events', operation: 'create' as const },
159
+ { pageName: 'organisations', operation: 'update' as const }
160
+ ];
161
+
162
+ testCases.forEach(({ pageName, operation }) => {
163
+ const { unmount } = render(
164
+ <PagePermissionGuard
165
+ pageName={pageName}
166
+ operation={operation}
167
+ scope={{ organisationId: 'org-123' }}
168
+ >
169
+ <div data-testid={`content-${operation}:${pageName}`}>Content for {operation}:{pageName}</div>
406
170
  </PagePermissionGuard>
407
171
  );
408
- };
409
-
410
- const { rerender } = render(<TestComponent />);
411
-
412
- // Simulate many re-renders with changing supabase reference
413
- for (let i = 0; i < 10; i++) {
414
- act(() => {
415
- rerender(<TestComponent />);
416
- });
417
- }
418
172
 
419
- // Wait for final state
420
- await waitFor(() => {
421
- expect(screen.getByTestId('protected-content')).toBeInTheDocument();
173
+ expect(screen.getByTestId(`content-${operation}:${pageName}`)).toBeInTheDocument();
174
+ unmount();
422
175
  });
423
-
424
- // Verify no infinite loops despite changing supabase reference
425
- expect(callCount).toBeLessThanOrEqual(5); // Should be very few calls
426
- expect(renderCount).toBeLessThanOrEqual(15); // Should be reasonable
427
176
  });
428
177
 
429
- it('should handle multiple rapid permission changes', async () => {
430
- let canValue = true;
431
- let callCount = 0;
432
-
433
- mockUseCan.mockImplementation(() => {
434
- callCount++;
435
- // Toggle permission on each call
436
- canValue = !canValue;
437
- return {
438
- can: canValue,
439
- isLoading: false,
440
- error: null
441
- };
178
+ it('handles different scope configurations', () => {
179
+ mockUseCan.mockReturnValue({
180
+ can: true,
181
+ isLoading: false,
182
+ error: null,
442
183
  });
443
-
444
- const TestComponent = () => {
445
- renderCount++;
446
- return (
447
- <PagePermissionGuard pageName="meals" operation="read">
448
- <div data-testid="protected-content">Protected Content</div>
184
+
185
+ const scopes = [
186
+ { organisationId: 'org-123' },
187
+ { organisationId: 'org-123', eventId: 'event-456' },
188
+ { organisationId: 'org-123', eventId: 'event-456', appId: 'app-789' },
189
+ ];
190
+
191
+ scopes.forEach((scope, index) => {
192
+ const { unmount } = render(
193
+ <PagePermissionGuard
194
+ pageName="users"
195
+ operation="read"
196
+ scope={scope}
197
+ >
198
+ <div data-testid={`content-${index}`}>Content {index}</div>
449
199
  </PagePermissionGuard>
450
200
  );
451
- };
452
-
453
- const { rerender } = render(<TestComponent />);
454
201
 
455
- // Simulate rapid permission changes
456
- for (let i = 0; i < 8; i++) {
457
- act(() => {
458
- rerender(<TestComponent />);
459
- });
460
- await new Promise(resolve => setTimeout(resolve, 5));
461
- }
462
-
463
- // Should eventually stabilize
464
- await waitFor(() => {
465
- // Either content or denied should be visible, not loading
466
- const hasContent = screen.queryByTestId('protected-content');
467
- const hasDenied = screen.queryByText('Access Denied');
468
- const hasLoading = screen.queryByText('Checking permissions...');
469
-
470
- expect(hasContent || hasDenied).toBeTruthy();
471
- expect(hasLoading).not.toBeInTheDocument();
202
+ expect(screen.getByTestId(`content-${index}`)).toBeInTheDocument();
203
+ unmount();
472
204
  });
473
-
474
- // Verify reasonable number of calls
475
- expect(callCount).toBeLessThanOrEqual(10);
476
205
  });
477
206
  });
478
- });
207
+ });
@@ -409,9 +409,8 @@ describe('PagePermissionGuard Component', () => {
409
409
  it('handles app resolution errors in production', async () => {
410
410
  mockGetCurrentAppName.mockReturnValue(null);
411
411
 
412
- // Set NODE_ENV to production
413
- const originalEnv = process.env.NODE_ENV;
414
- process.env.NODE_ENV = 'production';
412
+ // Mock import.meta.env.MODE for production using vi.stubEnv
413
+ vi.stubEnv('MODE', 'production');
415
414
 
416
415
  render(
417
416
  <PagePermissionGuard
@@ -427,8 +426,8 @@ describe('PagePermissionGuard Component', () => {
427
426
  expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
428
427
  });
429
428
 
430
- // Restore NODE_ENV
431
- process.env.NODE_ENV = originalEnv;
429
+ // Restore environment
430
+ vi.unstubAllEnvs();
432
431
  });
433
432
 
434
433
  it('validates app ID format in production', async () => {