@jmruthers/pace-core 0.5.54 → 0.5.55

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-4T627QFJ.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-MZBUOP4P.js → chunk-5BSLGBYI.js} +4 -3
  9. package/dist/chunk-5BSLGBYI.js.map +1 -0
  10. package/dist/{chunk-I5Z3QH5X.js → chunk-66C4BSAY.js} +2 -2
  11. package/dist/{chunk-I5Z3QH5X.js.map → chunk-66C4BSAY.js.map} +1 -1
  12. package/dist/{chunk-MYP2EGHX.js → chunk-AJ2KMES7.js} +21 -14
  13. package/dist/chunk-AJ2KMES7.js.map +1 -0
  14. package/dist/{chunk-EL2O4IUX.js → chunk-AQFRLC7K.js} +16 -24
  15. package/dist/{chunk-EL2O4IUX.js.map → chunk-AQFRLC7K.js.map} +1 -1
  16. package/dist/{chunk-7BNPOCLL.js → chunk-B2WTCLCV.js} +6 -2
  17. package/dist/chunk-B2WTCLCV.js.map +1 -0
  18. package/dist/{chunk-WJARTBCT.js → chunk-D7ARGIA3.js} +16 -7
  19. package/dist/chunk-D7ARGIA3.js.map +1 -0
  20. package/dist/{chunk-NRK4AIHQ.js → chunk-KBRACSJI.js} +3 -3
  21. package/dist/{chunk-NYUJ4FJR.js → chunk-KJDPSM64.js} +7 -7
  22. package/dist/chunk-KJDPSM64.js.map +1 -0
  23. package/dist/{chunk-GWSBHC4J.js → chunk-KLPVOPRI.js} +261 -38
  24. package/dist/chunk-KLPVOPRI.js.map +1 -0
  25. package/dist/{chunk-TRIZ7IB7.js → chunk-MPQDF75X.js} +148 -288
  26. package/dist/chunk-MPQDF75X.js.map +1 -0
  27. package/dist/{chunk-MSFACPQQ.js → chunk-PAEM3OWN.js} +11 -11
  28. package/dist/{chunk-MSFACPQQ.js.map → chunk-PAEM3OWN.js.map} +1 -1
  29. package/dist/{chunk-GIO7BFE7.js → chunk-RQD3D2CO.js} +66 -169
  30. package/dist/{chunk-GIO7BFE7.js.map → chunk-RQD3D2CO.js.map} +1 -1
  31. package/dist/{chunk-YDJW5XTN.js → chunk-STT7INZR.js} +25 -1
  32. package/dist/chunk-STT7INZR.js.map +1 -0
  33. package/dist/{chunk-6MTY77WU.js → chunk-TNMXZLDR.js} +3 -3
  34. package/dist/{chunk-BC3S53OZ.js → chunk-UQE2Y64H.js} +30 -14
  35. package/dist/chunk-UQE2Y64H.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-SS3E6QLB.js → chunk-YNUBMSMV.js} +2 -2
  39. package/dist/chunk-YNUBMSMV.js.map +1 -0
  40. package/dist/{chunk-NZ655MWE.js → chunk-ZOD2ZY6X.js} +5 -4
  41. package/dist/chunk-ZOD2ZY6X.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 +18 -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-4T627QFJ.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-KBRACSJI.js.map} +0 -0
  396. /package/dist/{chunk-6MTY77WU.js.map → chunk-TNMXZLDR.js.map} +0 -0
@@ -1,542 +1,411 @@
1
1
  /**
2
- * @file usePermissionCache Hook Tests
3
- * @description Comprehensive tests for the usePermissionCache hook
2
+ * @file Permission Cache Hook Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/usePermissionCache
5
+ * @since 0.3.0
6
+ *
7
+ * Comprehensive tests for the usePermissionCache hook covering all critical functionality.
4
8
  */
5
9
 
6
- import { renderHook, act } from '@testing-library/react';
7
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
+ import { renderHook, waitFor, act } from '@testing-library/react';
11
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
8
12
  import { usePermissionCache } from './usePermissionCache';
9
13
  import { useRBAC } from '../rbac/hooks/useRBAC';
10
- import type { Operation } from '../rbac/types';
11
14
 
12
- // Mock the useRBAC hook
15
+ // Mock the RBAC hook
13
16
  vi.mock('../rbac/hooks/useRBAC', () => ({
14
17
  useRBAC: vi.fn()
15
18
  }));
16
19
 
17
- const mockUseRBAC = vi.mocked(useRBAC);
18
-
19
- describe('usePermissionCache Hook', () => {
20
- const mockUser = {
21
- id: 'user-123',
22
- email: 'test@example.com'
23
- };
24
-
25
- const mockRBACContext = {
26
- user: mockUser,
27
- globalRole: null,
28
- organisationRole: null,
29
- eventAppRole: null,
30
- hasPermission: vi.fn(),
31
- hasGlobalPermission: vi.fn(),
32
- isSuperAdmin: false,
33
- isOrgAdmin: false,
34
- isEventAdmin: false,
35
- canManageOrganisation: false,
36
- canManageEvent: false,
37
- isLoading: false,
38
- error: null
39
- };
20
+ describe('usePermissionCache', () => {
21
+ const mockUseRBAC = vi.mocked(useRBAC);
22
+
23
+ // Create stable mock objects to prevent unnecessary re-renders
24
+ const stableMockRBAC = {
25
+ hasPermission: vi.fn().mockResolvedValue(true),
26
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
27
+ isOrgAdmin: vi.fn().mockResolvedValue(false),
28
+ isEventAdmin: vi.fn().mockResolvedValue(false),
29
+ // Add other required properties
30
+ } as any;
40
31
 
41
32
  beforeEach(() => {
42
33
  vi.clearAllMocks();
43
- mockUseRBAC.mockReturnValue(mockRBACContext);
44
- vi.useFakeTimers();
34
+ // Reset the mock functions to return the expected values
35
+ stableMockRBAC.hasPermission.mockResolvedValue(true);
36
+ stableMockRBAC.isSuperAdmin.mockResolvedValue(false);
37
+ stableMockRBAC.isOrgAdmin.mockResolvedValue(false);
38
+ stableMockRBAC.isEventAdmin.mockResolvedValue(false);
39
+ mockUseRBAC.mockReturnValue(stableMockRBAC);
45
40
  });
46
41
 
47
42
  afterEach(() => {
48
- vi.useRealTimers();
43
+ vi.restoreAllMocks();
49
44
  });
50
45
 
51
- describe('Initialization', () => {
46
+ describe('Hook Initialization', () => {
52
47
  it('initializes with default configuration', () => {
53
48
  const { result } = renderHook(() => usePermissionCache());
54
49
 
55
- expect(result.current.checkPermission).toBeDefined();
56
- expect(result.current.checkMultiplePermissions).toBeDefined();
57
- expect(result.current.getCachedPermissions).toBeDefined();
58
- expect(result.current.invalidateCache).toBeDefined();
50
+ expect(result.current).toBeDefined();
51
+ expect(result.current.checkPermission).toBeInstanceOf(Function);
52
+ expect(result.current.checkMultiplePermissions).toBeInstanceOf(Function);
53
+ expect(result.current.getCachedPermissions).toBeInstanceOf(Function);
54
+ expect(result.current.invalidateCache).toBeInstanceOf(Function);
59
55
  expect(result.current.getDebugInfo).toBeDefined();
60
- expect(result.current.getAuditTrail).toBeDefined();
61
56
  });
62
57
 
63
58
  it('initializes with custom configuration', () => {
64
59
  const customConfig = {
65
60
  defaultTTL: 10000,
66
61
  maxCacheSize: 500,
67
- enableLogging: false,
62
+ enableLogging: true,
68
63
  enableAuditTrail: false
69
64
  };
70
65
 
71
66
  const { result } = renderHook(() => usePermissionCache(customConfig));
72
67
 
73
- expect(result.current.checkPermission).toBeDefined();
74
- expect(result.current.checkMultiplePermissions).toBeDefined();
75
- expect(result.current.getCachedPermissions).toBeDefined();
76
- expect(result.current.invalidateCache).toBeDefined();
68
+ expect(result.current).toBeDefined();
77
69
  expect(result.current.getDebugInfo).toBeDefined();
78
- expect(result.current.getAuditTrail).toBeDefined();
70
+ });
71
+
72
+ it('depends on useRBAC hook', () => {
73
+ renderHook(() => usePermissionCache());
74
+ expect(mockUseRBAC).toHaveBeenCalled();
79
75
  });
80
76
  });
81
77
 
82
78
  describe('Permission Checking', () => {
83
- it('checks permission and caches result', async () => {
84
- mockRBACContext.hasPermission.mockResolvedValue(true);
85
-
79
+ it('checks single permission and caches result', async () => {
86
80
  const { result } = renderHook(() => usePermissionCache());
87
81
 
88
- const permission = await result.current.checkPermission('read', 'dashboard');
82
+ const hasPermission = await result.current.checkPermission('read', 'users');
83
+ expect(hasPermission).toBe(true);
84
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledWith('read', 'users');
89
85
 
90
- expect(permission).toBe(true);
91
- expect(mockRBACContext.hasPermission).toHaveBeenCalledWith('read', 'dashboard');
86
+ // Check that result is cached
87
+ const cachedResult = await result.current.checkPermission('read', 'users');
88
+ expect(cachedResult).toBe(true);
92
89
  });
93
90
 
94
- it('returns cached result on subsequent calls', async () => {
95
- mockRBACContext.hasPermission.mockResolvedValue(true);
96
-
91
+ it('checks multiple permissions in batch', async () => {
97
92
  const { result } = renderHook(() => usePermissionCache());
98
93
 
99
- // First call
100
- const firstResult = await result.current.checkPermission('read', 'dashboard');
101
- expect(firstResult).toBe(true);
102
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(1);
94
+ const permissions = [
95
+ ['read', 'users'],
96
+ ['create', 'users'],
97
+ ['update', 'users'],
98
+ ['delete', 'users']
99
+ ];
103
100
 
104
- // Second call should use cache
105
- const secondResult = await result.current.checkPermission('read', 'dashboard');
106
- expect(secondResult).toBe(true);
107
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(1);
101
+ const results = await result.current.checkMultiplePermissions(permissions);
102
+ expect(results).toHaveLength(4);
103
+ expect(results.every(r => r.hasPermission)).toBe(true);
108
104
  });
109
105
 
110
- it('handles permission check errors', async () => {
111
- mockRBACContext.hasPermission.mockRejectedValue(new Error('Permission check failed'));
106
+ it('handles permission check errors gracefully', async () => {
107
+ mockUseRBAC.mockReturnValue({
108
+ hasPermission: vi.fn().mockRejectedValue(new Error('Permission check failed')),
109
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
110
+ isOrgAdmin: vi.fn().mockResolvedValue(false),
111
+ isEventAdmin: vi.fn().mockResolvedValue(false),
112
+ // Add other required properties
113
+ } as any);
112
114
 
113
115
  const { result } = renderHook(() => usePermissionCache());
114
116
 
115
- const permission = await result.current.checkPermission('read', 'dashboard');
116
-
117
- expect(permission).toBe(false);
117
+ const hasPermission = await result.current.checkPermission('read', 'users');
118
+ expect(hasPermission).toBe(false);
118
119
  });
119
120
 
120
- it('handles different operations and page IDs', async () => {
121
- mockRBACContext.hasPermission
122
- .mockResolvedValueOnce(true)
123
- .mockResolvedValueOnce(false)
124
- .mockResolvedValueOnce(true);
121
+ it('handles missing RBAC hook gracefully', () => {
122
+ mockUseRBAC.mockReturnValue(null as any);
125
123
 
126
124
  const { result } = renderHook(() => usePermissionCache());
127
125
 
128
- const readPermission = await result.current.checkPermission('read', 'dashboard');
129
- const createPermission = await result.current.checkPermission('create', 'events');
130
- const updatePermission = await result.current.checkPermission('update', 'users');
131
-
132
- expect(readPermission).toBe(true);
133
- expect(createPermission).toBe(false);
134
- expect(updatePermission).toBe(true);
126
+ expect(result.current).toBeDefined();
127
+ expect(result.current.checkPermission).toBeInstanceOf(Function);
135
128
  });
136
129
  });
137
130
 
138
- describe('Multiple Permission Checking', () => {
139
- it('checks multiple permissions efficiently', async () => {
140
- mockRBACContext.hasPermission
141
- .mockResolvedValueOnce(true)
142
- .mockResolvedValueOnce(false)
143
- .mockResolvedValueOnce(true);
144
-
131
+ describe('Cache Management', () => {
132
+ it('caches permission results with TTL', async () => {
145
133
  const { result } = renderHook(() => usePermissionCache());
146
134
 
147
- const permissions = [
148
- ['read', 'dashboard'],
149
- ['create', 'events'],
150
- ['update', 'users']
151
- ] as Array<[Operation, string]>;
152
-
153
- const results = await result.current.checkMultiplePermissions(permissions);
154
-
155
- expect(results).toHaveLength(3);
156
- expect(results[0].hasPermission).toBe(true);
157
- expect(results[1].hasPermission).toBe(false);
158
- expect(results[2].hasPermission).toBe(true);
159
- expect(results[0].cached).toBe(false);
160
- expect(results[1].cached).toBe(false);
161
- expect(results[2].cached).toBe(false);
135
+ // First check
136
+ await result.current.checkPermission('read', 'users');
137
+
138
+ // Second check should use cache
139
+ await result.current.checkPermission('read', 'users');
140
+
141
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledTimes(1);
162
142
  });
163
143
 
164
- it('uses cache for previously checked permissions', async () => {
165
- mockRBACContext.hasPermission.mockResolvedValue(true);
166
-
167
- const { result } = renderHook(() => usePermissionCache());
168
-
169
- // First check individual permission
170
- await result.current.checkPermission('read', 'dashboard');
144
+ it('expires cached results after TTL', async () => {
145
+ const { result } = renderHook(() => usePermissionCache({
146
+ defaultTTL: 100 // 100ms TTL
147
+ }));
171
148
 
172
- // Then check multiple permissions including the cached one
173
- const permissions = [
174
- ['read', 'dashboard'], // Should be cached
175
- ['create', 'events'] // Should be fresh
176
- ] as Array<[Operation, string]>;
177
-
178
- const results = await result.current.checkMultiplePermissions(permissions);
179
-
180
- expect(results[0].cached).toBe(true);
181
- expect(results[1].cached).toBe(false);
182
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(2); // Once for each unique permission
149
+ // First check
150
+ await result.current.checkPermission('read', 'users');
151
+
152
+ // Wait for TTL to expire
153
+ await new Promise(resolve => setTimeout(resolve, 150));
154
+
155
+ // Second check should not use cache
156
+ await result.current.checkPermission('read', 'users');
157
+
158
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledTimes(2);
183
159
  });
184
- });
185
-
186
- describe('Cache Management', () => {
187
- it('invalidates all cache', async () => {
188
- mockRBACContext.hasPermission.mockResolvedValue(true);
189
160
 
161
+ it('invalidates cache correctly', async () => {
190
162
  const { result } = renderHook(() => usePermissionCache());
191
163
 
192
- // Check a permission to populate cache
193
- await result.current.checkPermission('read', 'dashboard');
194
-
164
+ // Check permission
165
+ await result.current.checkPermission('read', 'users');
166
+
195
167
  // Invalidate cache
196
168
  result.current.invalidateCache();
197
-
198
- // Check same permission again - should not be cached
199
- await result.current.checkPermission('read', 'dashboard');
200
-
201
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(2);
202
- });
203
-
204
- it('invalidates cache by pattern', async () => {
205
- mockRBACContext.hasPermission.mockResolvedValue(true);
206
-
207
- const { result } = renderHook(() => usePermissionCache());
208
-
209
- // Check multiple permissions
210
- await result.current.checkPermission('read', 'dashboard');
211
- await result.current.checkPermission('read', 'events');
212
- await result.current.checkPermission('create', 'dashboard');
213
-
214
- // Invalidate only 'read' permissions
215
- result.current.invalidateCache('read:.*');
216
-
217
- // Check permissions again
218
- await result.current.checkPermission('read', 'dashboard');
219
- await result.current.checkPermission('create', 'dashboard');
220
-
221
- // 'read' permissions should be fresh, 'create' should be cached
222
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(4); // 2 fresh + 2 cached
169
+
170
+ // Check again - should not use cache
171
+ await result.current.checkPermission('read', 'users');
172
+
173
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledTimes(2);
223
174
  });
224
175
 
225
- it('enforces maximum cache size', async () => {
226
- const config = { maxCacheSize: 2 };
227
- mockRBACContext.hasPermission.mockResolvedValue(true);
228
-
229
- const { result } = renderHook(() => usePermissionCache(config));
230
-
231
- // Add more permissions than max cache size
232
- await result.current.checkPermission('read', 'page1');
233
- await result.current.checkPermission('read', 'page2');
234
- await result.current.checkPermission('read', 'page3');
176
+ it('respects max cache size', async () => {
177
+ const { result } = renderHook(() => usePermissionCache({
178
+ maxCacheSize: 2
179
+ }));
235
180
 
236
- // Should have evicted oldest entries
181
+ // Fill cache beyond max size
182
+ await result.current.checkPermission('read', 'users');
183
+ await result.current.checkPermission('create', 'users');
184
+ await result.current.checkPermission('update', 'users');
185
+ await result.current.checkPermission('delete', 'users');
186
+
187
+ // Check that cache size is respected
237
188
  const debugInfo = result.current.getDebugInfo();
238
189
  expect(debugInfo.cacheSize).toBeLessThanOrEqual(2);
239
190
  });
240
191
  });
241
192
 
242
- describe('TTL and Expiration', () => {
243
- it('respects TTL for cache entries', async () => {
244
- const config = { defaultTTL: 1000 }; // 1 second
245
- mockRBACContext.hasPermission.mockResolvedValue(true);
246
-
247
- const { result } = renderHook(() => usePermissionCache(config));
248
-
249
- // Check permission
250
- await result.current.checkPermission('read', 'dashboard');
251
-
252
- // Fast-forward time past TTL
253
- act(() => {
254
- vi.advanceTimersByTime(1001);
255
- });
256
-
257
- // Check same permission again - should not be cached
258
- await result.current.checkPermission('read', 'dashboard');
259
-
260
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(2);
261
- });
262
-
263
- it('respects custom TTL for individual permissions', async () => {
264
- mockRBACContext.hasPermission.mockResolvedValue(true);
265
-
266
- const { result } = renderHook(() => usePermissionCache());
267
-
268
- // Check permission with custom TTL
269
- await result.current.checkPermission('read', 'dashboard', 500);
270
-
271
- // Fast-forward time past custom TTL but before default TTL
272
- act(() => {
273
- vi.advanceTimersByTime(501);
274
- });
275
-
276
- // Check same permission again - should not be cached
277
- await result.current.checkPermission('read', 'dashboard');
278
-
279
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(2);
280
- });
281
- });
282
-
283
- describe('Concurrent Requests', () => {
284
- it('handles concurrent requests for same permission', async () => {
285
- mockRBACContext.hasPermission.mockResolvedValue(true);
286
-
287
- const { result } = renderHook(() => usePermissionCache());
288
-
289
- // Make multiple concurrent requests for same permission
290
- const promises = [
291
- result.current.checkPermission('read', 'dashboard'),
292
- result.current.checkPermission('read', 'dashboard'),
293
- result.current.checkPermission('read', 'dashboard')
294
- ];
295
-
296
- const results = await Promise.all(promises);
297
-
298
- expect(results).toEqual([true, true, true]);
299
- // Should only call the underlying function once
300
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(1);
301
- });
302
-
303
- it('handles concurrent requests for different permissions', async () => {
304
- mockRBACContext.hasPermission
305
- .mockResolvedValueOnce(true)
306
- .mockResolvedValueOnce(false)
307
- .mockResolvedValueOnce(true);
308
-
309
- const { result } = renderHook(() => usePermissionCache());
310
-
311
- // Make concurrent requests for different permissions
312
- const promises = [
313
- result.current.checkPermission('read', 'dashboard'),
314
- result.current.checkPermission('create', 'events'),
315
- result.current.checkPermission('update', 'users')
316
- ];
317
-
318
- const results = await Promise.all(promises);
319
-
320
- expect(results).toEqual([true, false, true]);
321
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(3);
322
- });
323
- });
324
-
325
193
  describe('Debug Information', () => {
326
- it('provides debug information', async () => {
327
- mockRBACContext.hasPermission.mockResolvedValue(true);
328
-
194
+ it('provides debug information', () => {
329
195
  const { result } = renderHook(() => usePermissionCache());
330
196
 
331
- // Check some permissions
332
- await result.current.checkPermission('read', 'dashboard');
333
- await result.current.checkPermission('create', 'events');
334
-
335
197
  const debugInfo = result.current.getDebugInfo();
336
-
337
- expect(debugInfo.cacheSize).toBe(2);
198
+ expect(debugInfo).toBeDefined();
199
+ expect(debugInfo.cacheSize).toBe(0);
338
200
  expect(debugInfo.cacheHits).toBe(0);
339
- expect(debugInfo.cacheMisses).toBe(2);
340
- expect(debugInfo.totalChecks).toBe(2);
341
- expect(debugInfo.averageResponseTime).toBeGreaterThan(0);
342
- expect(debugInfo.lastInvalidation).toBeGreaterThan(0);
201
+ expect(debugInfo.cacheMisses).toBe(0);
202
+ expect(debugInfo.totalChecks).toBe(0);
203
+ expect(debugInfo.averageResponseTime).toBe(0);
204
+ expect(debugInfo.lastInvalidation).toBe(0);
343
205
  });
344
206
 
345
207
  it('tracks cache hits and misses', async () => {
346
- mockRBACContext.hasPermission.mockResolvedValue(true);
347
-
348
208
  const { result } = renderHook(() => usePermissionCache());
349
209
 
350
- // First call - cache miss
351
- await result.current.checkPermission('read', 'dashboard');
210
+ // First check - cache miss
211
+ await result.current.checkPermission('read', 'users');
212
+
213
+ // Second check - cache hit
214
+ await result.current.checkPermission('read', 'users');
352
215
 
353
- // Second call - cache hit
354
- await result.current.checkPermission('read', 'dashboard');
355
-
356
216
  const debugInfo = result.current.getDebugInfo();
357
-
358
- expect(debugInfo.cacheHits).toBe(1);
359
217
  expect(debugInfo.cacheMisses).toBe(1);
218
+ expect(debugInfo.cacheHits).toBe(1);
360
219
  expect(debugInfo.totalChecks).toBe(2);
361
220
  });
362
- });
363
-
364
- describe('Audit Trail', () => {
365
- it('records audit trail when enabled', async () => {
366
- const config = { enableAuditTrail: true };
367
- mockRBACContext.hasPermission.mockResolvedValue(true);
368
-
369
- const { result } = renderHook(() => usePermissionCache(config));
370
-
371
- await result.current.checkPermission('read', 'dashboard');
372
221
 
373
- const auditTrail = result.current.getAuditTrail();
222
+ it('tracks average response time', async () => {
223
+ const { result } = renderHook(() => usePermissionCache());
374
224
 
375
- expect(auditTrail).toHaveLength(1);
376
- expect(auditTrail[0].operation).toBe('read');
377
- expect(auditTrail[0].pageId).toBe('dashboard');
378
- expect(auditTrail[0].result).toBe(true);
379
- expect(auditTrail[0].cached).toBe(false);
380
- expect(auditTrail[0].userId).toBe('user-123');
225
+ // Mock slow permission check
226
+ mockUseRBAC.mockReturnValue({
227
+ hasPermission: vi.fn().mockImplementation(() =>
228
+ new Promise(resolve => setTimeout(() => resolve(true), 100))
229
+ ),
230
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
231
+ isOrgAdmin: vi.fn().mockResolvedValue(false),
232
+ isEventAdmin: vi.fn().mockResolvedValue(false),
233
+ // Add other required properties
234
+ } as any);
235
+
236
+ await result.current.checkPermission('read', 'users');
237
+
238
+ const debugInfo = result.current.getDebugInfo();
239
+ expect(debugInfo.averageResponseTime).toBeGreaterThan(0);
381
240
  });
241
+ });
382
242
 
383
- it('does not record audit trail when disabled', async () => {
384
- const config = { enableAuditTrail: false };
385
- mockRBACContext.hasPermission.mockResolvedValue(true);
386
-
387
- const { result } = renderHook(() => usePermissionCache(config));
243
+ describe('Audit Trail', () => {
244
+ it('logs permission checks when audit trail is enabled', async () => {
245
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
388
246
 
389
- await result.current.checkPermission('read', 'dashboard');
247
+ const { result } = renderHook(() => usePermissionCache({
248
+ enableAuditTrail: true,
249
+ enableLogging: true
250
+ }));
390
251
 
391
- const auditTrail = result.current.getAuditTrail();
252
+ await result.current.checkPermission('read', 'users');
253
+
254
+ expect(consoleSpy).toHaveBeenCalledWith(
255
+ expect.stringContaining('[PermissionCache] read:users = true (fresh)')
256
+ );
392
257
 
393
- expect(auditTrail).toHaveLength(0);
258
+ consoleSpy.mockRestore();
394
259
  });
395
260
 
396
- it('limits audit trail size', async () => {
397
- const config = { enableAuditTrail: true };
398
- mockRBACContext.hasPermission.mockResolvedValue(true);
399
-
400
- const { result } = renderHook(() => usePermissionCache(config));
261
+ it('does not log when audit trail is disabled', async () => {
262
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
401
263
 
402
- // Generate more than 500 audit entries
403
- for (let i = 0; i < 600; i++) {
404
- await result.current.checkPermission('read', `page${i}`);
405
- }
264
+ const { result } = renderHook(() => usePermissionCache({
265
+ enableAuditTrail: false,
266
+ enableLogging: false
267
+ }));
406
268
 
407
- const auditTrail = result.current.getAuditTrail();
269
+ await result.current.checkPermission('read', 'users');
270
+
271
+ expect(consoleSpy).not.toHaveBeenCalled();
408
272
 
409
- expect(auditTrail.length).toBeLessThanOrEqual(500);
273
+ consoleSpy.mockRestore();
410
274
  });
411
275
  });
412
276
 
413
- describe('Cached Permissions', () => {
414
- it('returns cached permissions for a page', async () => {
415
- mockRBACContext.hasPermission.mockResolvedValue(true);
416
-
277
+ describe('Performance Optimization', () => {
278
+ it('batches multiple permission checks efficiently', async () => {
417
279
  const { result } = renderHook(() => usePermissionCache());
418
280
 
419
- // Check multiple permissions for same page
420
- await result.current.checkPermission('read', 'dashboard');
421
- await result.current.checkPermission('create', 'dashboard');
422
- await result.current.checkPermission('update', 'dashboard');
281
+ const permissions = [
282
+ ['read', 'users'],
283
+ ['create', 'users'],
284
+ ['update', 'users'],
285
+ ['delete', 'users']
286
+ ];
423
287
 
424
- const cachedPermissions = result.current.getCachedPermissions('dashboard');
288
+ const startTime = Date.now();
289
+ await result.current.checkMultiplePermissions(permissions);
290
+ const endTime = Date.now();
425
291
 
426
- expect(cachedPermissions).toHaveLength(3);
427
- expect(cachedPermissions.every(p => p.hasPermission)).toBe(true);
292
+ // Should complete quickly due to batching
293
+ expect(endTime - startTime).toBeLessThan(1000);
428
294
  });
429
295
 
430
- it('returns empty array for page with no cached permissions', () => {
431
- const { result } = renderHook(() => usePermissionCache());
432
-
433
- const cachedPermissions = result.current.getCachedPermissions('nonexistent');
296
+ it('uses memoization to prevent unnecessary re-renders', () => {
297
+ let renderCount = 0;
298
+
299
+ const TestComponent = () => {
300
+ renderCount++;
301
+ const cache = usePermissionCache();
302
+ // Test that the same functions are returned (memoized)
303
+ expect(cache.checkPermission).toBeInstanceOf(Function);
304
+ expect(cache.getDebugInfo).toBeInstanceOf(Function);
305
+ return null;
306
+ };
434
307
 
435
- expect(cachedPermissions).toHaveLength(0);
308
+ const { rerender } = renderHook(() => TestComponent());
309
+ const initialRenderCount = renderCount;
310
+
311
+ // Re-render with same props
312
+ rerender();
313
+
314
+ // The hook will re-render, but the functions should be memoized
315
+ expect(renderCount).toBe(initialRenderCount + 1);
436
316
  });
437
317
  });
438
318
 
439
319
  describe('Error Handling', () => {
440
- it('handles permission check failures gracefully', async () => {
441
- mockRBACContext.hasPermission.mockRejectedValue(new Error('Database error'));
320
+ it('handles RBAC hook errors gracefully', async () => {
321
+ mockUseRBAC.mockReturnValue({
322
+ hasPermission: vi.fn().mockRejectedValue(new Error('RBAC error')),
323
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
324
+ isOrgAdmin: vi.fn().mockResolvedValue(false),
325
+ isEventAdmin: vi.fn().mockResolvedValue(false),
326
+ // Add other required properties
327
+ } as any);
442
328
 
443
329
  const { result } = renderHook(() => usePermissionCache());
444
330
 
445
- const permission = await result.current.checkPermission('read', 'dashboard');
446
-
447
- expect(permission).toBe(false);
331
+ const hasPermission = await result.current.checkPermission('read', 'users');
332
+ expect(hasPermission).toBe(false);
448
333
  });
449
334
 
450
- it('handles multiple permission check failures', async () => {
451
- mockRBACContext.hasPermission.mockRejectedValue(new Error('Database error'));
452
-
335
+ it('handles invalid permission formats gracefully', async () => {
453
336
  const { result } = renderHook(() => usePermissionCache());
454
337
 
455
- const permissions = [
456
- ['read', 'dashboard'],
457
- ['create', 'events']
458
- ] as Array<[Operation, string]>;
338
+ const hasPermission = await result.current.checkPermission('', '');
339
+ expect(hasPermission).toBe(false);
340
+ });
459
341
 
460
- const results = await result.current.checkMultiplePermissions(permissions);
342
+ it('handles null/undefined parameters gracefully', async () => {
343
+ const { result } = renderHook(() => usePermissionCache());
461
344
 
462
- expect(results).toHaveLength(2);
463
- expect(results.every(r => r.hasPermission === false)).toBe(true);
345
+ const hasPermission = await result.current.checkPermission(null as any, null as any);
346
+ expect(hasPermission).toBe(false);
464
347
  });
465
348
  });
466
349
 
467
- describe('Configuration', () => {
468
- it('uses custom TTL configuration', async () => {
469
- const config = { defaultTTL: 2000 };
470
- mockRBACContext.hasPermission.mockResolvedValue(true);
471
-
472
- const { result } = renderHook(() => usePermissionCache(config));
473
-
474
- await result.current.checkPermission('read', 'dashboard');
475
-
476
- // Fast-forward time less than custom TTL
477
- act(() => {
478
- vi.advanceTimersByTime(1000);
479
- });
350
+ describe('Cache Configuration', () => {
351
+ it('uses custom TTL when provided', async () => {
352
+ const { result } = renderHook(() => usePermissionCache({
353
+ defaultTTL: 200
354
+ }));
480
355
 
481
- // Should still be cached
482
- await result.current.checkPermission('read', 'dashboard');
483
-
484
- expect(mockRBACContext.hasPermission).toHaveBeenCalledTimes(1);
356
+ await result.current.checkPermission('read', 'users');
357
+
358
+ // Wait for custom TTL to expire
359
+ await new Promise(resolve => setTimeout(resolve, 250));
360
+
361
+ // Should not use cache
362
+ await result.current.checkPermission('read', 'users');
363
+
364
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledTimes(2);
485
365
  });
486
366
 
487
- it('uses custom max cache size', async () => {
488
- const config = { maxCacheSize: 1 };
489
- mockRBACContext.hasPermission.mockResolvedValue(true);
490
-
491
- const { result } = renderHook(() => usePermissionCache(config));
492
-
493
- await result.current.checkPermission('read', 'page1');
494
- await result.current.checkPermission('read', 'page2');
367
+ it('respects max cache size configuration', async () => {
368
+ const { result } = renderHook(() => usePermissionCache({
369
+ maxCacheSize: 1
370
+ }));
495
371
 
372
+ // Fill cache beyond max size
373
+ await result.current.checkPermission('read', 'users');
374
+ await result.current.checkPermission('create', 'users');
375
+
496
376
  const debugInfo = result.current.getDebugInfo();
497
377
  expect(debugInfo.cacheSize).toBeLessThanOrEqual(1);
498
378
  });
499
-
500
- it('respects logging configuration', async () => {
501
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
502
- const config = { enableLogging: true };
503
- mockRBACContext.hasPermission.mockResolvedValue(true);
504
-
505
- const { result } = renderHook(() => usePermissionCache(config));
506
-
507
- await result.current.checkPermission('read', 'dashboard');
508
-
509
- expect(consoleSpy).toHaveBeenCalledWith(
510
- expect.stringMatching(/\[PermissionCache\].*read:dashboard.*true.*fresh/)
511
- );
512
-
513
- consoleSpy.mockRestore();
514
- });
515
379
  });
516
380
 
517
- describe('Cleanup', () => {
518
- it('cleans up expired entries periodically', async () => {
519
- const config = { defaultTTL: 1000 };
520
- mockRBACContext.hasPermission.mockResolvedValue(true);
381
+ describe('Integration with RBAC', () => {
382
+ it('integrates with useRBAC hook correctly', () => {
383
+ renderHook(() => usePermissionCache());
384
+ expect(mockUseRBAC).toHaveBeenCalled();
385
+ });
521
386
 
522
- const { result } = renderHook(() => usePermissionCache(config));
387
+ it('passes correct parameters to RBAC hook', async () => {
388
+ const { result } = renderHook(() => usePermissionCache());
523
389
 
524
- await result.current.checkPermission('read', 'dashboard');
390
+ await result.current.checkPermission('read', 'users');
391
+
392
+ expect(mockUseRBAC().hasPermission).toHaveBeenCalledWith('read', 'users');
393
+ });
525
394
 
526
- // Fast-forward time past TTL
527
- act(() => {
528
- vi.advanceTimersByTime(1001);
529
- });
395
+ it('handles RBAC hook state changes', async () => {
396
+ const mockHasPermission = vi.fn().mockResolvedValue(true);
397
+ mockUseRBAC.mockReturnValue({
398
+ hasPermission: mockHasPermission,
399
+ isSuperAdmin: vi.fn().mockResolvedValue(false),
400
+ isOrgAdmin: vi.fn().mockResolvedValue(false),
401
+ isEventAdmin: vi.fn().mockResolvedValue(false),
402
+ // Add other required properties
403
+ } as any);
530
404
 
531
- // Trigger cleanup by advancing time further to trigger the interval
532
- act(() => {
533
- vi.advanceTimersByTime(2000);
534
- });
405
+ const { result } = renderHook(() => usePermissionCache());
535
406
 
536
- // The cleanup might not happen immediately due to interval timing
537
- // Just verify the cache has some entries and the cleanup mechanism works
538
- const debugInfo = result.current.getDebugInfo();
539
- expect(debugInfo.cacheSize).toBeGreaterThanOrEqual(0);
407
+ await result.current.checkPermission('read', 'users');
408
+ expect(mockHasPermission).toHaveBeenCalledWith('read', 'users');
540
409
  });
541
410
  });
542
- });
411
+ });