@jmruthers/pace-core 0.5.110 → 0.5.111

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 (230) hide show
  1. package/dist/{AuthService-DrHrvXNZ.d.ts → AuthService-CVgsgtaZ.d.ts} +8 -0
  2. package/dist/{DataTable-D3BK2FCN.js → DataTable-5W2HVLLV.js} +8 -8
  3. package/dist/{UnifiedAuthProvider-A7I23UCN.js → UnifiedAuthProvider-LUM3QLS5.js} +3 -3
  4. package/dist/{api-PIE4JRFS.js → api-SIZPFBFX.js} +5 -3
  5. package/dist/{audit-65VNHEV2.js → audit-5JI5T3SL.js} +2 -2
  6. package/dist/{chunk-3J5N2T2N.js → chunk-2BIDKXQU.js} +113 -116
  7. package/dist/chunk-2BIDKXQU.js.map +1 -0
  8. package/dist/{chunk-AWK2FAUN.js → chunk-ACYQNYHB.js} +7 -7
  9. package/dist/{chunk-D6MEKC27.js → chunk-EFVQBYFN.js} +2 -2
  10. package/dist/{chunk-EZ64QG2I.js → chunk-I5YM5BGS.js} +2 -2
  11. package/dist/{chunk-Q7APDV6H.js → chunk-IWJYNWXN.js} +13 -5
  12. package/dist/chunk-IWJYNWXN.js.map +1 -0
  13. package/dist/{chunk-YFMENCR4.js → chunk-JE2GFA3O.js} +3 -3
  14. package/dist/{chunk-AUXS7XSO.js → chunk-MW73E7SP.js} +35 -11
  15. package/dist/chunk-MW73E7SP.js.map +1 -0
  16. package/dist/{chunk-XRSP3H52.js → chunk-PXXS26G5.js} +57 -23
  17. package/dist/chunk-PXXS26G5.js.map +1 -0
  18. package/dist/{chunk-HGZSO43Y.js → chunk-TD4BXGPE.js} +4 -4
  19. package/dist/{chunk-EYSXQ756.js → chunk-TDFBX7KJ.js} +2 -2
  20. package/dist/{chunk-HADXAZT3.js → chunk-UGVU7L7N.js} +52 -90
  21. package/dist/chunk-UGVU7L7N.js.map +1 -0
  22. package/dist/{chunk-2W4WKJVF.js → chunk-X7SPKHYZ.js} +290 -255
  23. package/dist/chunk-X7SPKHYZ.js.map +1 -0
  24. package/dist/{chunk-7GBEBJLR.js → chunk-ZL45MG76.js} +45 -37
  25. package/dist/chunk-ZL45MG76.js.map +1 -0
  26. package/dist/components.js +10 -10
  27. package/dist/hooks.d.ts +11 -1
  28. package/dist/hooks.js +9 -7
  29. package/dist/hooks.js.map +1 -1
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +13 -13
  32. package/dist/providers.d.ts +2 -2
  33. package/dist/providers.js +2 -2
  34. package/dist/rbac/index.d.ts +13 -8
  35. package/dist/rbac/index.js +9 -9
  36. package/dist/utils.js +1 -1
  37. package/docs/api/classes/ColumnFactory.md +1 -1
  38. package/docs/api/classes/ErrorBoundary.md +1 -1
  39. package/docs/api/classes/InvalidScopeError.md +4 -4
  40. package/docs/api/classes/MissingUserContextError.md +4 -4
  41. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  42. package/docs/api/classes/PermissionDeniedError.md +4 -4
  43. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  44. package/docs/api/classes/RBACAuditManager.md +8 -8
  45. package/docs/api/classes/RBACCache.md +8 -8
  46. package/docs/api/classes/RBACEngine.md +4 -4
  47. package/docs/api/classes/RBACError.md +4 -4
  48. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  49. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  50. package/docs/api/classes/StorageUtils.md +1 -1
  51. package/docs/api/enums/FileCategory.md +1 -1
  52. package/docs/api/interfaces/AggregateConfig.md +1 -1
  53. package/docs/api/interfaces/ButtonProps.md +1 -1
  54. package/docs/api/interfaces/CardProps.md +1 -1
  55. package/docs/api/interfaces/ColorPalette.md +1 -1
  56. package/docs/api/interfaces/ColorShade.md +1 -1
  57. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  58. package/docs/api/interfaces/DataRecord.md +1 -1
  59. package/docs/api/interfaces/DataTableAction.md +1 -1
  60. package/docs/api/interfaces/DataTableColumn.md +1 -1
  61. package/docs/api/interfaces/DataTableProps.md +1 -1
  62. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  63. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  64. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  65. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  66. package/docs/api/interfaces/FileMetadata.md +1 -1
  67. package/docs/api/interfaces/FileReference.md +1 -1
  68. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  69. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  70. package/docs/api/interfaces/FileUploadProps.md +1 -1
  71. package/docs/api/interfaces/FooterProps.md +1 -1
  72. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  73. package/docs/api/interfaces/InputProps.md +1 -1
  74. package/docs/api/interfaces/LabelProps.md +1 -1
  75. package/docs/api/interfaces/LoginFormProps.md +1 -1
  76. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  77. package/docs/api/interfaces/NavigationContextType.md +1 -1
  78. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  79. package/docs/api/interfaces/NavigationItem.md +1 -1
  80. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  81. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  82. package/docs/api/interfaces/Organisation.md +1 -1
  83. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  84. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  85. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  86. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  87. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  88. package/docs/api/interfaces/PaceLoginPageProps.md +4 -4
  89. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  90. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  91. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  92. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  93. package/docs/api/interfaces/PaletteData.md +1 -1
  94. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  95. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  96. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  97. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  98. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  99. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  100. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  101. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  102. package/docs/api/interfaces/RBACConfig.md +1 -1
  103. package/docs/api/interfaces/RBACLogger.md +1 -1
  104. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  105. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  106. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  107. package/docs/api/interfaces/RouteConfig.md +19 -6
  108. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  109. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  110. package/docs/api/interfaces/StorageConfig.md +1 -1
  111. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  112. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  113. package/docs/api/interfaces/StorageListOptions.md +1 -1
  114. package/docs/api/interfaces/StorageListResult.md +1 -1
  115. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  116. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  117. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  118. package/docs/api/interfaces/StyleImport.md +1 -1
  119. package/docs/api/interfaces/SwitchProps.md +1 -1
  120. package/docs/api/interfaces/ToastActionElement.md +1 -1
  121. package/docs/api/interfaces/ToastProps.md +1 -1
  122. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  123. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  124. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  125. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  127. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  128. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  129. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  130. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  131. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  132. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  133. package/docs/api/interfaces/UserEventAccess.md +1 -1
  134. package/docs/api/interfaces/UserMenuProps.md +1 -1
  135. package/docs/api/interfaces/UserProfile.md +1 -1
  136. package/docs/api/modules.md +36 -36
  137. package/docs/api-reference/hooks.md +8 -4
  138. package/docs/architecture/rpc-function-standards.md +3 -1
  139. package/docs/best-practices/common-patterns.md +3 -3
  140. package/docs/best-practices/deployment.md +10 -4
  141. package/docs/best-practices/performance.md +11 -3
  142. package/docs/core-concepts/organisations.md +8 -8
  143. package/docs/core-concepts/permissions.md +133 -72
  144. package/docs/migration/rbac-migration.md +65 -66
  145. package/docs/rbac/advanced-patterns.md +15 -22
  146. package/docs/rbac/examples.md +12 -12
  147. package/docs/rbac/getting-started.md +3 -3
  148. package/docs/rbac/troubleshooting.md +2 -1
  149. package/package.json +1 -1
  150. package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +913 -0
  151. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +609 -0
  152. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +434 -0
  153. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +120 -0
  154. package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +519 -0
  155. package/src/components/DataTable/examples/__tests__/HierarchicalActionsExample.test.tsx +316 -0
  156. package/src/components/DataTable/examples/__tests__/InitialPageSizeExample.test.tsx +211 -0
  157. package/src/components/FileUpload/FileUpload.tsx +2 -8
  158. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +193 -63
  159. package/src/components/PaceAppLayout/PaceAppLayout.tsx +102 -135
  160. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +41 -2
  161. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +61 -6
  162. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +71 -21
  163. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +113 -41
  164. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +155 -45
  165. package/src/components/PaceLoginPage/PaceLoginPage.tsx +30 -1
  166. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +63 -5
  167. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +156 -72
  168. package/src/hooks/__tests__/useRBAC.unit.test.ts +4 -38
  169. package/src/hooks/index.ts +1 -1
  170. package/src/hooks/useFileDisplay.ts +51 -0
  171. package/src/hooks/usePermissionCache.test.ts +112 -68
  172. package/src/hooks/usePermissionCache.ts +55 -15
  173. package/src/rbac/README.md +81 -39
  174. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +3 -3
  175. package/src/rbac/__tests__/engine.comprehensive.test.ts +15 -6
  176. package/src/rbac/__tests__/rbac-core.test.tsx +1 -1
  177. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +57 -4
  178. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +3 -2
  179. package/src/rbac/adapters.tsx +4 -4
  180. package/src/rbac/api.test.ts +37 -13
  181. package/src/rbac/api.ts +25 -8
  182. package/src/rbac/audit.test.ts +2 -2
  183. package/src/rbac/audit.ts +14 -5
  184. package/src/rbac/cache.test.ts +12 -0
  185. package/src/rbac/cache.ts +29 -9
  186. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -1
  187. package/src/rbac/components/NavigationGuard.tsx +14 -14
  188. package/src/rbac/components/NavigationProvider.test.tsx +1 -1
  189. package/src/rbac/components/PagePermissionGuard.tsx +4 -3
  190. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -1
  191. package/src/rbac/components/PermissionEnforcer.tsx +19 -15
  192. package/src/rbac/components/RoleBasedRouter.tsx +16 -9
  193. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +123 -107
  194. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1 -1
  195. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +121 -103
  196. package/src/rbac/docs/event-based-apps.md +6 -6
  197. package/src/rbac/engine.ts +12 -2
  198. package/src/rbac/hooks/useCan.test.ts +29 -2
  199. package/src/rbac/hooks/usePermissions.test.ts +25 -25
  200. package/src/rbac/hooks/usePermissions.ts +47 -23
  201. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -8
  202. package/src/rbac/hooks/useRBAC.test.ts +3 -40
  203. package/src/rbac/hooks/useRBAC.ts +0 -55
  204. package/src/rbac/hooks/useResolvedScope.ts +23 -31
  205. package/src/rbac/permissions.test.ts +11 -7
  206. package/src/rbac/security.test.ts +2 -2
  207. package/src/rbac/security.ts +22 -7
  208. package/src/rbac/types.test.ts +2 -2
  209. package/src/rbac/types.ts +1 -2
  210. package/src/services/EventService.ts +41 -13
  211. package/src/services/__tests__/EventService.test.ts +25 -4
  212. package/src/services/interfaces/IEventService.ts +1 -0
  213. package/src/utils/file-reference.ts +9 -0
  214. package/dist/chunk-2W4WKJVF.js.map +0 -1
  215. package/dist/chunk-3J5N2T2N.js.map +0 -1
  216. package/dist/chunk-7GBEBJLR.js.map +0 -1
  217. package/dist/chunk-AUXS7XSO.js.map +0 -1
  218. package/dist/chunk-HADXAZT3.js.map +0 -1
  219. package/dist/chunk-Q7APDV6H.js.map +0 -1
  220. package/dist/chunk-XRSP3H52.js.map +0 -1
  221. /package/dist/{DataTable-D3BK2FCN.js.map → DataTable-5W2HVLLV.js.map} +0 -0
  222. /package/dist/{UnifiedAuthProvider-A7I23UCN.js.map → UnifiedAuthProvider-LUM3QLS5.js.map} +0 -0
  223. /package/dist/{api-PIE4JRFS.js.map → api-SIZPFBFX.js.map} +0 -0
  224. /package/dist/{audit-65VNHEV2.js.map → audit-5JI5T3SL.js.map} +0 -0
  225. /package/dist/{chunk-AWK2FAUN.js.map → chunk-ACYQNYHB.js.map} +0 -0
  226. /package/dist/{chunk-D6MEKC27.js.map → chunk-EFVQBYFN.js.map} +0 -0
  227. /package/dist/{chunk-EZ64QG2I.js.map → chunk-I5YM5BGS.js.map} +0 -0
  228. /package/dist/{chunk-YFMENCR4.js.map → chunk-JE2GFA3O.js.map} +0 -0
  229. /package/dist/{chunk-HGZSO43Y.js.map → chunk-TD4BXGPE.js.map} +0 -0
  230. /package/dist/{chunk-EYSXQ756.js.map → chunk-TDFBX7KJ.js.map} +0 -0
@@ -11,12 +11,12 @@ import { render, screen, waitFor } from '@testing-library/react';
11
11
  import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
12
12
  import { ReactNode } from 'react';
13
13
  import { NavigationGuard } from '../NavigationGuard';
14
- import { useCan } from '../../hooks';
14
+ import { useMultiplePermissions } from '../../hooks/usePermissions';
15
15
  import { useUnifiedAuth } from '../../../providers/UnifiedAuthProvider';
16
16
 
17
17
  // Mock the RBAC hooks
18
- vi.mock('../../hooks', () => ({
19
- useCan: vi.fn()
18
+ vi.mock('../../hooks/usePermissions', () => ({
19
+ useMultiplePermissions: vi.fn()
20
20
  }));
21
21
 
22
22
  // Mock the auth provider
@@ -68,7 +68,7 @@ const TestLoading = () => (
68
68
  );
69
69
 
70
70
  describe('NavigationGuard Component', () => {
71
- const mockUseCan = vi.mocked(useCan);
71
+ const mockUseMultiplePermissions = vi.mocked(useMultiplePermissions);
72
72
  const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
73
73
  const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
74
74
 
@@ -83,19 +83,21 @@ describe('NavigationGuard Component', () => {
83
83
  supabase: {} as any
84
84
  });
85
85
 
86
- mockUseCan.mockReturnValue({
87
- can: true,
86
+ mockUseMultiplePermissions.mockReturnValue({
87
+ results: { 'read:dashboard': true } as Record<string, boolean>,
88
88
  isLoading: false,
89
- error: null
89
+ error: null,
90
+ refetch: vi.fn()
90
91
  });
91
92
  });
92
93
 
93
94
  describe('Rendering', () => {
94
95
  it('renders children when permission is granted', async () => {
95
- mockUseCan.mockReturnValue({
96
- can: true,
96
+ mockUseMultiplePermissions.mockReturnValue({
97
+ results: { 'read:dashboard': true } as Record<string, boolean>,
97
98
  isLoading: false,
98
- error: null
99
+ error: null,
100
+ refetch: vi.fn()
99
101
  });
100
102
 
101
103
  render(
@@ -111,10 +113,11 @@ describe('NavigationGuard Component', () => {
111
113
  });
112
114
 
113
115
  it('renders fallback when permission is denied', async () => {
114
- mockUseCan.mockReturnValue({
115
- can: false,
116
+ mockUseMultiplePermissions.mockReturnValue({
117
+ results: { 'read:dashboard': false } as Record<string, boolean>,
116
118
  isLoading: false,
117
- error: null
119
+ error: null,
120
+ refetch: vi.fn()
118
121
  });
119
122
 
120
123
  render(
@@ -133,10 +136,11 @@ describe('NavigationGuard Component', () => {
133
136
  });
134
137
 
135
138
  it('shows loading state during permission check', () => {
136
- mockUseCan.mockReturnValue({
137
- can: false,
139
+ mockUseMultiplePermissions.mockReturnValue({
140
+ results: {} as Record<string, boolean>,
138
141
  isLoading: true,
139
- error: null
142
+ error: null,
143
+ refetch: vi.fn()
140
144
  });
141
145
 
142
146
  render(
@@ -153,10 +157,11 @@ describe('NavigationGuard Component', () => {
153
157
  });
154
158
 
155
159
  it('uses default fallback when none provided', async () => {
156
- mockUseCan.mockReturnValue({
157
- can: false,
160
+ mockUseMultiplePermissions.mockReturnValue({
161
+ results: { 'read:dashboard': false } as Record<string, boolean>,
158
162
  isLoading: false,
159
- error: null
163
+ error: null,
164
+ refetch: vi.fn()
160
165
  });
161
166
 
162
167
  render(
@@ -171,10 +176,11 @@ describe('NavigationGuard Component', () => {
171
176
  });
172
177
 
173
178
  it('uses default loading when none provided', () => {
174
- mockUseCan.mockReturnValue({
175
- can: false,
179
+ mockUseMultiplePermissions.mockReturnValue({
180
+ results: {} as Record<string, boolean>,
176
181
  isLoading: true,
177
- error: null
182
+ error: null,
183
+ refetch: vi.fn()
178
184
  });
179
185
 
180
186
  render(
@@ -189,10 +195,11 @@ describe('NavigationGuard Component', () => {
189
195
 
190
196
  describe('Permission Checking', () => {
191
197
  it('enforces navigation permissions correctly', async () => {
192
- mockUseCan.mockReturnValue({
193
- can: true,
198
+ mockUseMultiplePermissions.mockReturnValue({
199
+ results: { 'read:dashboard': true } as Record<string, boolean>,
194
200
  isLoading: false,
195
- error: null
201
+ error: null,
202
+ refetch: vi.fn()
196
203
  });
197
204
 
198
205
  render(
@@ -205,14 +212,13 @@ describe('NavigationGuard Component', () => {
205
212
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
206
213
  }, { interval: 10 });
207
214
 
208
- expect(mockUseCan).toHaveBeenCalledWith(
215
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
209
216
  'user-123',
210
217
  expect.objectContaining({
211
218
  organisationId: 'org-123',
212
219
  eventId: 'event-123'
213
220
  }),
214
- 'read:dashboard',
215
- 'dashboard',
221
+ ['read:dashboard'],
216
222
  true
217
223
  );
218
224
  });
@@ -223,10 +229,11 @@ describe('NavigationGuard Component', () => {
223
229
  permissions: ['read:dashboard', 'write:dashboard'] as const
224
230
  };
225
231
 
226
- mockUseCan.mockReturnValue({
227
- can: true,
232
+ mockUseMultiplePermissions.mockReturnValue({
233
+ results: { 'read:dashboard': true, 'write:dashboard': true } as Record<string, boolean>,
228
234
  isLoading: false,
229
- error: null
235
+ error: null,
236
+ refetch: vi.fn()
230
237
  });
231
238
 
232
239
  render(
@@ -239,15 +246,14 @@ describe('NavigationGuard Component', () => {
239
246
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
240
247
  }, { interval: 10 });
241
248
 
242
- // Should check the first permission as representative
243
- expect(mockUseCan).toHaveBeenCalledWith(
249
+ // Should check all permissions when multiple are provided
250
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
244
251
  'user-123',
245
252
  expect.objectContaining({
246
253
  organisationId: 'org-123',
247
254
  eventId: 'event-123'
248
255
  }),
249
- 'read:dashboard',
250
- 'dashboard',
256
+ ['read:dashboard', 'write:dashboard'],
251
257
  true
252
258
  );
253
259
  });
@@ -258,10 +264,11 @@ describe('NavigationGuard Component', () => {
258
264
  permissions: [] as const
259
265
  };
260
266
 
261
- mockUseCan.mockReturnValue({
262
- can: true,
267
+ mockUseMultiplePermissions.mockReturnValue({
268
+ results: { 'read:dashboard': true } as Record<string, boolean>,
263
269
  isLoading: false,
264
- error: null
270
+ error: null,
271
+ refetch: vi.fn()
265
272
  });
266
273
 
267
274
  render(
@@ -277,10 +284,11 @@ describe('NavigationGuard Component', () => {
277
284
 
278
285
  it('handles permission checking errors gracefully', async () => {
279
286
  const error = new Error('Permission check failed');
280
- mockUseCan.mockReturnValue({
281
- can: false,
287
+ mockUseMultiplePermissions.mockReturnValue({
288
+ results: { 'read:dashboard': false } as Record<string, boolean>,
282
289
  isLoading: false,
283
- error
290
+ error,
291
+ refetch: vi.fn()
284
292
  });
285
293
 
286
294
  render(
@@ -306,10 +314,11 @@ describe('NavigationGuard Component', () => {
306
314
  appId: 'custom-app'
307
315
  };
308
316
 
309
- mockUseCan.mockReturnValue({
310
- can: true,
317
+ mockUseMultiplePermissions.mockReturnValue({
318
+ results: { 'read:dashboard': true } as Record<string, boolean>,
311
319
  isLoading: false,
312
- error: null
320
+ error: null,
321
+ refetch: vi.fn()
313
322
  });
314
323
 
315
324
  render(
@@ -325,20 +334,20 @@ describe('NavigationGuard Component', () => {
325
334
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
326
335
  }, { interval: 10 });
327
336
 
328
- expect(mockUseCan).toHaveBeenCalledWith(
337
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
329
338
  'user-123',
330
339
  customScope,
331
- 'read:dashboard',
332
- 'dashboard',
340
+ ['read:dashboard'],
333
341
  true
334
342
  );
335
343
  });
336
344
 
337
345
  it('resolves scope from organisation and event context', async () => {
338
- mockUseCan.mockReturnValue({
339
- can: true,
346
+ mockUseMultiplePermissions.mockReturnValue({
347
+ results: { 'read:dashboard': true } as Record<string, boolean>,
340
348
  isLoading: false,
341
- error: null
349
+ error: null,
350
+ refetch: vi.fn()
342
351
  });
343
352
 
344
353
  render(
@@ -351,14 +360,13 @@ describe('NavigationGuard Component', () => {
351
360
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
352
361
  }, { interval: 10 });
353
362
 
354
- expect(mockUseCan).toHaveBeenCalledWith(
363
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
355
364
  'user-123',
356
365
  expect.objectContaining({
357
366
  organisationId: 'org-123',
358
367
  eventId: 'event-123'
359
368
  }),
360
- 'read:dashboard',
361
- 'dashboard',
369
+ ['read:dashboard'],
362
370
  true
363
371
  );
364
372
  });
@@ -371,10 +379,11 @@ describe('NavigationGuard Component', () => {
371
379
  supabase: {} as any
372
380
  });
373
381
 
374
- mockUseCan.mockReturnValue({
375
- can: true,
382
+ mockUseMultiplePermissions.mockReturnValue({
383
+ results: { 'read:dashboard': true } as Record<string, boolean>,
376
384
  isLoading: false,
377
- error: null
385
+ error: null,
386
+ refetch: vi.fn()
378
387
  });
379
388
 
380
389
  render(
@@ -387,14 +396,13 @@ describe('NavigationGuard Component', () => {
387
396
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
388
397
  }, { interval: 10 });
389
398
 
390
- expect(mockUseCan).toHaveBeenCalledWith(
399
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
391
400
  'user-123',
392
401
  expect.objectContaining({
393
402
  organisationId: 'org-123',
394
403
  eventId: undefined
395
404
  }),
396
- 'read:dashboard',
397
- 'dashboard',
405
+ ['read:dashboard'],
398
406
  true
399
407
  );
400
408
  });
@@ -413,10 +421,11 @@ describe('NavigationGuard Component', () => {
413
421
  appId: 'resolved-app'
414
422
  });
415
423
 
416
- mockUseCan.mockReturnValue({
417
- can: true,
424
+ mockUseMultiplePermissions.mockReturnValue({
425
+ results: { 'read:dashboard': true } as Record<string, boolean>,
418
426
  isLoading: false,
419
- error: null
427
+ error: null,
428
+ refetch: vi.fn()
420
429
  });
421
430
 
422
431
  render(
@@ -430,14 +439,13 @@ describe('NavigationGuard Component', () => {
430
439
  }, { interval: 10 });
431
440
 
432
441
  expect(mockCreateScopeFromEvent).toHaveBeenCalledWith({}, 'event-123');
433
- expect(mockUseCan).toHaveBeenCalledWith(
442
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
434
443
  'user-123',
435
444
  expect.objectContaining({
436
445
  organisationId: 'resolved-org',
437
446
  eventId: 'event-123'
438
447
  }),
439
- 'read:dashboard',
440
- 'dashboard',
448
+ ['read:dashboard'],
441
449
  true
442
450
  );
443
451
  });
@@ -494,10 +502,11 @@ describe('NavigationGuard Component', () => {
494
502
  it('prevents bypassing in strict mode', async () => {
495
503
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
496
504
 
497
- mockUseCan.mockReturnValue({
498
- can: false,
505
+ mockUseMultiplePermissions.mockReturnValue({
506
+ results: { 'read:dashboard': false } as Record<string, boolean>,
499
507
  isLoading: false,
500
- error: null
508
+ error: null,
509
+ refetch: vi.fn()
501
510
  });
502
511
 
503
512
  render(
@@ -529,10 +538,11 @@ describe('NavigationGuard Component', () => {
529
538
  it('logs navigation access attempts for audit', async () => {
530
539
  const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
531
540
 
532
- mockUseCan.mockReturnValue({
533
- can: false,
541
+ mockUseMultiplePermissions.mockReturnValue({
542
+ results: { 'read:dashboard': false } as Record<string, boolean>,
534
543
  isLoading: false,
535
- error: null
544
+ error: null,
545
+ refetch: vi.fn()
536
546
  });
537
547
 
538
548
  render(
@@ -565,10 +575,11 @@ describe('NavigationGuard Component', () => {
565
575
  it('calls onDenied callback when access is denied', async () => {
566
576
  const onDeniedSpy = vi.fn();
567
577
 
568
- mockUseCan.mockReturnValue({
569
- can: false,
578
+ mockUseMultiplePermissions.mockReturnValue({
579
+ results: { 'read:dashboard': false } as Record<string, boolean>,
570
580
  isLoading: false,
571
- error: null
581
+ error: null,
582
+ refetch: vi.fn()
572
583
  });
573
584
 
574
585
  render(
@@ -591,10 +602,11 @@ describe('NavigationGuard Component', () => {
591
602
  it('does not call onDenied when access is granted', async () => {
592
603
  const onDeniedSpy = vi.fn();
593
604
 
594
- mockUseCan.mockReturnValue({
595
- can: true,
605
+ mockUseMultiplePermissions.mockReturnValue({
606
+ results: { 'read:dashboard': true } as Record<string, boolean>,
596
607
  isLoading: false,
597
- error: null
608
+ error: null,
609
+ refetch: vi.fn()
598
610
  });
599
611
 
600
612
  render(
@@ -618,10 +630,11 @@ describe('NavigationGuard Component', () => {
618
630
  it('respects strictMode setting', async () => {
619
631
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
620
632
 
621
- mockUseCan.mockReturnValue({
622
- can: false,
633
+ mockUseMultiplePermissions.mockReturnValue({
634
+ results: { 'read:dashboard': false } as Record<string, boolean>,
623
635
  isLoading: false,
624
- error: null
636
+ error: null,
637
+ refetch: vi.fn()
625
638
  });
626
639
 
627
640
  render(
@@ -648,10 +661,11 @@ describe('NavigationGuard Component', () => {
648
661
  it('respects auditLog setting', async () => {
649
662
  const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
650
663
 
651
- mockUseCan.mockReturnValue({
652
- can: false,
664
+ mockUseMultiplePermissions.mockReturnValue({
665
+ results: { 'read:dashboard': false } as Record<string, boolean>,
653
666
  isLoading: false,
654
- error: null
667
+ error: null,
668
+ refetch: vi.fn()
655
669
  });
656
670
 
657
671
  render(
@@ -676,10 +690,11 @@ describe('NavigationGuard Component', () => {
676
690
  });
677
691
 
678
692
  it('respects requireAll setting', async () => {
679
- mockUseCan.mockReturnValue({
680
- can: true,
693
+ mockUseMultiplePermissions.mockReturnValue({
694
+ results: { 'read:dashboard': true } as Record<string, boolean>,
681
695
  isLoading: false,
682
- error: null
696
+ error: null,
697
+ refetch: vi.fn()
683
698
  });
684
699
 
685
700
  render(
@@ -696,14 +711,13 @@ describe('NavigationGuard Component', () => {
696
711
  }, { interval: 10 });
697
712
 
698
713
  // Should still check the first permission as representative
699
- expect(mockUseCan).toHaveBeenCalledWith(
714
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
700
715
  'user-123',
701
716
  expect.objectContaining({
702
717
  organisationId: 'org-123',
703
718
  eventId: 'event-123'
704
719
  }),
705
- 'read:dashboard',
706
- 'dashboard',
720
+ ['read:dashboard'],
707
721
  true
708
722
  );
709
723
  });
@@ -718,10 +732,11 @@ describe('NavigationGuard Component', () => {
718
732
  supabase: {} as any
719
733
  });
720
734
 
721
- mockUseCan.mockReturnValue({
722
- can: false,
735
+ mockUseMultiplePermissions.mockReturnValue({
736
+ results: { 'read:dashboard': false } as Record<string, boolean>,
723
737
  isLoading: false,
724
- error: null
738
+ error: null,
739
+ refetch: vi.fn()
725
740
  });
726
741
 
727
742
  render(
@@ -737,24 +752,24 @@ describe('NavigationGuard Component', () => {
737
752
  expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
738
753
  }, { interval: 10 });
739
754
 
740
- expect(mockUseCan).toHaveBeenCalledWith(
755
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
741
756
  '',
742
757
  expect.objectContaining({
743
758
  organisationId: 'org-123',
744
759
  eventId: 'event-123'
745
760
  }),
746
- 'read:dashboard',
747
- 'dashboard',
761
+ ['read:dashboard'],
748
762
  true
749
763
  );
750
764
  });
751
765
 
752
766
  it('handles permission check errors', async () => {
753
767
  const error = new Error('Database connection failed');
754
- mockUseCan.mockReturnValue({
755
- can: false,
768
+ mockUseMultiplePermissions.mockReturnValue({
769
+ results: { 'read:dashboard': false } as Record<string, boolean>,
756
770
  isLoading: false,
757
- error
771
+ error,
772
+ refetch: vi.fn()
758
773
  });
759
774
 
760
775
  render(
@@ -785,10 +800,11 @@ describe('NavigationGuard Component', () => {
785
800
  hidden: false
786
801
  };
787
802
 
788
- mockUseCan.mockReturnValue({
789
- can: true,
803
+ mockUseMultiplePermissions.mockReturnValue({
804
+ results: { 'read:dashboard': true } as Record<string, boolean>,
790
805
  isLoading: false,
791
- error: null
806
+ error: null,
807
+ refetch: vi.fn()
792
808
  });
793
809
 
794
810
  render(
@@ -801,14 +817,13 @@ describe('NavigationGuard Component', () => {
801
817
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
802
818
  }, { interval: 10 });
803
819
 
804
- expect(mockUseCan).toHaveBeenCalledWith(
820
+ expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
805
821
  'user-123',
806
822
  expect.objectContaining({
807
823
  organisationId: 'org-123',
808
824
  eventId: 'event-123'
809
825
  }),
810
- 'read:settings',
811
- 'settings',
826
+ ['read:settings'],
812
827
  true
813
828
  );
814
829
  });
@@ -819,10 +834,11 @@ describe('NavigationGuard Component', () => {
819
834
  hidden: true
820
835
  };
821
836
 
822
- mockUseCan.mockReturnValue({
823
- can: true,
837
+ mockUseMultiplePermissions.mockReturnValue({
838
+ results: { 'read:dashboard': true } as Record<string, boolean>,
824
839
  isLoading: false,
825
- error: null
840
+ error: null,
841
+ refetch: vi.fn()
826
842
  });
827
843
 
828
844
  render(
@@ -947,7 +947,7 @@ describe('PagePermissionGuard Component', () => {
947
947
  expect(mockUseCan).toHaveBeenCalledWith(
948
948
  '',
949
949
  expect.objectContaining({
950
- organisationId: '',
950
+ organisationId: undefined, // Changed from empty string - undefined is used when not resolved
951
951
  eventId: undefined,
952
952
  appId: undefined // appId is optional and should be undefined when not resolved
953
953
  }),