@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.
- package/dist/{AuthService-DrHrvXNZ.d.ts → AuthService-CVgsgtaZ.d.ts} +8 -0
- package/dist/{DataTable-D3BK2FCN.js → DataTable-5W2HVLLV.js} +8 -8
- package/dist/{UnifiedAuthProvider-A7I23UCN.js → UnifiedAuthProvider-LUM3QLS5.js} +3 -3
- package/dist/{api-PIE4JRFS.js → api-SIZPFBFX.js} +5 -3
- package/dist/{audit-65VNHEV2.js → audit-5JI5T3SL.js} +2 -2
- package/dist/{chunk-3J5N2T2N.js → chunk-2BIDKXQU.js} +113 -116
- package/dist/chunk-2BIDKXQU.js.map +1 -0
- package/dist/{chunk-AWK2FAUN.js → chunk-ACYQNYHB.js} +7 -7
- package/dist/{chunk-D6MEKC27.js → chunk-EFVQBYFN.js} +2 -2
- package/dist/{chunk-EZ64QG2I.js → chunk-I5YM5BGS.js} +2 -2
- package/dist/{chunk-Q7APDV6H.js → chunk-IWJYNWXN.js} +13 -5
- package/dist/chunk-IWJYNWXN.js.map +1 -0
- package/dist/{chunk-YFMENCR4.js → chunk-JE2GFA3O.js} +3 -3
- package/dist/{chunk-AUXS7XSO.js → chunk-MW73E7SP.js} +35 -11
- package/dist/chunk-MW73E7SP.js.map +1 -0
- package/dist/{chunk-XRSP3H52.js → chunk-PXXS26G5.js} +57 -23
- package/dist/chunk-PXXS26G5.js.map +1 -0
- package/dist/{chunk-HGZSO43Y.js → chunk-TD4BXGPE.js} +4 -4
- package/dist/{chunk-EYSXQ756.js → chunk-TDFBX7KJ.js} +2 -2
- package/dist/{chunk-HADXAZT3.js → chunk-UGVU7L7N.js} +52 -90
- package/dist/chunk-UGVU7L7N.js.map +1 -0
- package/dist/{chunk-2W4WKJVF.js → chunk-X7SPKHYZ.js} +290 -255
- package/dist/chunk-X7SPKHYZ.js.map +1 -0
- package/dist/{chunk-7GBEBJLR.js → chunk-ZL45MG76.js} +45 -37
- package/dist/chunk-ZL45MG76.js.map +1 -0
- package/dist/components.js +10 -10
- package/dist/hooks.d.ts +11 -1
- package/dist/hooks.js +9 -7
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -13
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +13 -8
- package/dist/rbac/index.js +9 -9
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +8 -8
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +4 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +19 -6
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +36 -36
- package/docs/api-reference/hooks.md +8 -4
- package/docs/architecture/rpc-function-standards.md +3 -1
- package/docs/best-practices/common-patterns.md +3 -3
- package/docs/best-practices/deployment.md +10 -4
- package/docs/best-practices/performance.md +11 -3
- package/docs/core-concepts/organisations.md +8 -8
- package/docs/core-concepts/permissions.md +133 -72
- package/docs/migration/rbac-migration.md +65 -66
- package/docs/rbac/advanced-patterns.md +15 -22
- package/docs/rbac/examples.md +12 -12
- package/docs/rbac/getting-started.md +3 -3
- package/docs/rbac/troubleshooting.md +2 -1
- package/package.json +1 -1
- package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +913 -0
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +609 -0
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +434 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +120 -0
- package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +519 -0
- package/src/components/DataTable/examples/__tests__/HierarchicalActionsExample.test.tsx +316 -0
- package/src/components/DataTable/examples/__tests__/InitialPageSizeExample.test.tsx +211 -0
- package/src/components/FileUpload/FileUpload.tsx +2 -8
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +193 -63
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +102 -135
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +41 -2
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +61 -6
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +71 -21
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +113 -41
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +155 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +30 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +63 -5
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +156 -72
- package/src/hooks/__tests__/useRBAC.unit.test.ts +4 -38
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useFileDisplay.ts +51 -0
- package/src/hooks/usePermissionCache.test.ts +112 -68
- package/src/hooks/usePermissionCache.ts +55 -15
- package/src/rbac/README.md +81 -39
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +3 -3
- package/src/rbac/__tests__/engine.comprehensive.test.ts +15 -6
- package/src/rbac/__tests__/rbac-core.test.tsx +1 -1
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +57 -4
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +3 -2
- package/src/rbac/adapters.tsx +4 -4
- package/src/rbac/api.test.ts +37 -13
- package/src/rbac/api.ts +25 -8
- package/src/rbac/audit.test.ts +2 -2
- package/src/rbac/audit.ts +14 -5
- package/src/rbac/cache.test.ts +12 -0
- package/src/rbac/cache.ts +29 -9
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +14 -14
- package/src/rbac/components/NavigationProvider.test.tsx +1 -1
- package/src/rbac/components/PagePermissionGuard.tsx +4 -3
- package/src/rbac/components/PagePermissionProvider.test.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +19 -15
- package/src/rbac/components/RoleBasedRouter.tsx +16 -9
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +123 -107
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +121 -103
- package/src/rbac/docs/event-based-apps.md +6 -6
- package/src/rbac/engine.ts +12 -2
- package/src/rbac/hooks/useCan.test.ts +29 -2
- package/src/rbac/hooks/usePermissions.test.ts +25 -25
- package/src/rbac/hooks/usePermissions.ts +47 -23
- package/src/rbac/hooks/useRBAC.simple.test.ts +1 -8
- package/src/rbac/hooks/useRBAC.test.ts +3 -40
- package/src/rbac/hooks/useRBAC.ts +0 -55
- package/src/rbac/hooks/useResolvedScope.ts +23 -31
- package/src/rbac/permissions.test.ts +11 -7
- package/src/rbac/security.test.ts +2 -2
- package/src/rbac/security.ts +22 -7
- package/src/rbac/types.test.ts +2 -2
- package/src/rbac/types.ts +1 -2
- package/src/services/EventService.ts +41 -13
- package/src/services/__tests__/EventService.test.ts +25 -4
- package/src/services/interfaces/IEventService.ts +1 -0
- package/src/utils/file-reference.ts +9 -0
- package/dist/chunk-2W4WKJVF.js.map +0 -1
- package/dist/chunk-3J5N2T2N.js.map +0 -1
- package/dist/chunk-7GBEBJLR.js.map +0 -1
- package/dist/chunk-AUXS7XSO.js.map +0 -1
- package/dist/chunk-HADXAZT3.js.map +0 -1
- package/dist/chunk-Q7APDV6H.js.map +0 -1
- package/dist/chunk-XRSP3H52.js.map +0 -1
- /package/dist/{DataTable-D3BK2FCN.js.map → DataTable-5W2HVLLV.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A7I23UCN.js.map → UnifiedAuthProvider-LUM3QLS5.js.map} +0 -0
- /package/dist/{api-PIE4JRFS.js.map → api-SIZPFBFX.js.map} +0 -0
- /package/dist/{audit-65VNHEV2.js.map → audit-5JI5T3SL.js.map} +0 -0
- /package/dist/{chunk-AWK2FAUN.js.map → chunk-ACYQNYHB.js.map} +0 -0
- /package/dist/{chunk-D6MEKC27.js.map → chunk-EFVQBYFN.js.map} +0 -0
- /package/dist/{chunk-EZ64QG2I.js.map → chunk-I5YM5BGS.js.map} +0 -0
- /package/dist/{chunk-YFMENCR4.js.map → chunk-JE2GFA3O.js.map} +0 -0
- /package/dist/{chunk-HGZSO43Y.js.map → chunk-TD4BXGPE.js.map} +0 -0
- /package/dist/{chunk-EYSXQ756.js.map → chunk-TDFBX7KJ.js.map} +0 -0
|
@@ -54,13 +54,91 @@ vi.mock('../../providers/UnifiedAuthProvider', () => ({
|
|
|
54
54
|
useUnifiedAuth: vi.fn(() => mockUnifiedAuth),
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
|
+
// Mock useOrganisations hook
|
|
58
|
+
const mockSelectedOrganisation = {
|
|
59
|
+
id: 'org-123',
|
|
60
|
+
name: 'Test Organisation',
|
|
61
|
+
display_name: 'Test Organisation',
|
|
62
|
+
slug: 'test-org',
|
|
63
|
+
description: 'Test organisation',
|
|
64
|
+
subscription_tier: 'basic',
|
|
65
|
+
settings: {},
|
|
66
|
+
is_active: true,
|
|
67
|
+
created_at: '2023-01-01T00:00:00Z',
|
|
68
|
+
updated_at: '2023-01-01T00:00:00Z',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
vi.mock('../../hooks/useOrganisations', () => ({
|
|
72
|
+
useOrganisations: vi.fn(() => ({
|
|
73
|
+
selectedOrganisation: mockSelectedOrganisation,
|
|
74
|
+
organisations: [mockSelectedOrganisation],
|
|
75
|
+
userMemberships: [],
|
|
76
|
+
isLoading: false,
|
|
77
|
+
error: null,
|
|
78
|
+
hasValidOrganisationContext: true,
|
|
79
|
+
setSelectedOrganisation: vi.fn(),
|
|
80
|
+
switchOrganisation: vi.fn().mockResolvedValue(undefined),
|
|
81
|
+
getUserRole: vi.fn().mockReturnValue('member'),
|
|
82
|
+
validateOrganisationAccess: vi.fn().mockReturnValue(true),
|
|
83
|
+
ensureOrganisationContext: vi.fn().mockReturnValue(mockSelectedOrganisation),
|
|
84
|
+
refreshOrganisations: vi.fn().mockResolvedValue(undefined),
|
|
85
|
+
getPrimaryOrganisation: vi.fn().mockReturnValue(mockSelectedOrganisation),
|
|
86
|
+
isOrganisationSecure: vi.fn().mockReturnValue(true),
|
|
87
|
+
})),
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Mock useEvents hook (optional - wrapped in try/catch in component)
|
|
91
|
+
vi.mock('../../providers/EventsProvider', () => ({
|
|
92
|
+
useEvents: vi.fn(() => ({
|
|
93
|
+
selectedEvent: { event_id: 'event-123' },
|
|
94
|
+
events: [],
|
|
95
|
+
isLoading: false,
|
|
96
|
+
error: null,
|
|
97
|
+
})),
|
|
98
|
+
}));
|
|
99
|
+
|
|
57
100
|
// Mock RBAC functions
|
|
101
|
+
const mockIsPermitted = vi.fn().mockResolvedValue(true);
|
|
102
|
+
const mockIsPermittedCached = vi.fn().mockResolvedValue(true);
|
|
103
|
+
|
|
58
104
|
vi.mock('../../rbac/api', () => ({
|
|
59
|
-
isPermitted: vi.fn()
|
|
105
|
+
isPermitted: vi.fn(),
|
|
106
|
+
isPermittedCached: vi.fn(),
|
|
60
107
|
isSuperAdmin: vi.fn().mockResolvedValue(false),
|
|
61
108
|
setupRBAC: vi.fn(),
|
|
62
109
|
}));
|
|
63
110
|
|
|
111
|
+
// Mock useCan hook - this is what PaceAppLayout actually uses
|
|
112
|
+
const mockUseCan = vi.fn(() => ({
|
|
113
|
+
can: true,
|
|
114
|
+
isLoading: false,
|
|
115
|
+
error: null,
|
|
116
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// Mock RBAC hooks
|
|
120
|
+
const mockHasPermissionFn = vi.fn().mockResolvedValue(true);
|
|
121
|
+
vi.mock('../../rbac/hooks', () => ({
|
|
122
|
+
useRBAC: vi.fn(() => ({
|
|
123
|
+
hasPermission: mockHasPermissionFn,
|
|
124
|
+
isLoading: false,
|
|
125
|
+
error: null,
|
|
126
|
+
hasGlobalPermission: vi.fn().mockResolvedValue(true),
|
|
127
|
+
hasOrganisationPermission: vi.fn().mockResolvedValue(true),
|
|
128
|
+
hasEventPermission: vi.fn().mockResolvedValue(true),
|
|
129
|
+
globalRole: null,
|
|
130
|
+
organisationRoles: [],
|
|
131
|
+
eventRoles: [],
|
|
132
|
+
permissionMap: {},
|
|
133
|
+
})),
|
|
134
|
+
useCan: (...args: any[]) => mockUseCan(...args),
|
|
135
|
+
useResolvedScope: vi.fn(() => ({
|
|
136
|
+
resolvedScope: { organisationId: 'org-123', eventId: 'event-123', appId: 'app-123' },
|
|
137
|
+
isLoading: false,
|
|
138
|
+
error: null,
|
|
139
|
+
})),
|
|
140
|
+
}));
|
|
141
|
+
|
|
64
142
|
// Mock Header component
|
|
65
143
|
vi.mock('../Header', () => ({
|
|
66
144
|
Header: ({
|
|
@@ -151,10 +229,21 @@ describe('PaceAppLayout Component', () => {
|
|
|
151
229
|
vi.clearAllMocks();
|
|
152
230
|
// Reset location mock
|
|
153
231
|
mockLocation.pathname = '/dashboard';
|
|
154
|
-
// Reset RBAC mocks
|
|
155
|
-
|
|
232
|
+
// Reset RBAC hook mocks
|
|
233
|
+
mockHasPermissionFn.mockClear();
|
|
234
|
+
mockHasPermissionFn.mockResolvedValue(true);
|
|
235
|
+
mockUseCan.mockReturnValue({
|
|
236
|
+
can: true,
|
|
237
|
+
isLoading: false,
|
|
238
|
+
error: null,
|
|
239
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
240
|
+
});
|
|
241
|
+
// Reset RBAC API mocks
|
|
242
|
+
const { isPermitted, isPermittedCached, isSuperAdmin } = await import('../../rbac/api');
|
|
156
243
|
vi.mocked(isPermitted).mockReset();
|
|
157
244
|
vi.mocked(isPermitted).mockResolvedValue(true);
|
|
245
|
+
vi.mocked(isPermittedCached).mockReset();
|
|
246
|
+
vi.mocked(isPermittedCached).mockResolvedValue(true);
|
|
158
247
|
vi.mocked(isSuperAdmin).mockReset();
|
|
159
248
|
vi.mocked(isSuperAdmin).mockResolvedValue(false);
|
|
160
249
|
});
|
|
@@ -360,8 +449,13 @@ describe('PaceAppLayout Component', () => {
|
|
|
360
449
|
});
|
|
361
450
|
|
|
362
451
|
it('shows loading state when checking permissions', async () => {
|
|
363
|
-
|
|
364
|
-
|
|
452
|
+
// Mock useCan to return loading state
|
|
453
|
+
mockUseCan.mockReturnValueOnce({
|
|
454
|
+
can: false,
|
|
455
|
+
isLoading: true,
|
|
456
|
+
error: null,
|
|
457
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
458
|
+
});
|
|
365
459
|
|
|
366
460
|
renderWithProviders(
|
|
367
461
|
<TestWrapper>
|
|
@@ -374,8 +468,13 @@ describe('PaceAppLayout Component', () => {
|
|
|
374
468
|
});
|
|
375
469
|
|
|
376
470
|
it('shows permission error when check fails', async () => {
|
|
377
|
-
|
|
378
|
-
|
|
471
|
+
// Mock useCan to return an error state
|
|
472
|
+
mockUseCan.mockReturnValueOnce({
|
|
473
|
+
can: false,
|
|
474
|
+
isLoading: false,
|
|
475
|
+
error: new Error('Permission check failed'),
|
|
476
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
477
|
+
});
|
|
379
478
|
|
|
380
479
|
renderWithProviders(
|
|
381
480
|
<TestWrapper>
|
|
@@ -390,8 +489,13 @@ describe('PaceAppLayout Component', () => {
|
|
|
390
489
|
});
|
|
391
490
|
|
|
392
491
|
it('shows access denied when user lacks permission', async () => {
|
|
393
|
-
|
|
394
|
-
|
|
492
|
+
// Mock useCan to return false (no permission)
|
|
493
|
+
mockUseCan.mockReturnValueOnce({
|
|
494
|
+
can: false,
|
|
495
|
+
isLoading: false,
|
|
496
|
+
error: null,
|
|
497
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
498
|
+
});
|
|
395
499
|
|
|
396
500
|
renderWithProviders(
|
|
397
501
|
<TestWrapper>
|
|
@@ -406,11 +510,16 @@ describe('PaceAppLayout Component', () => {
|
|
|
406
510
|
});
|
|
407
511
|
|
|
408
512
|
it('shows custom permission fallback when provided', async () => {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
513
|
+
// Arrange
|
|
514
|
+
mockUseCan.mockReturnValueOnce({
|
|
515
|
+
can: false,
|
|
516
|
+
isLoading: false,
|
|
517
|
+
error: null,
|
|
518
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
519
|
+
});
|
|
412
520
|
const customFallback = <div data-testid="custom-fallback">Custom Access Denied</div>;
|
|
413
521
|
|
|
522
|
+
// Act
|
|
414
523
|
renderWithProviders(
|
|
415
524
|
<TestWrapper>
|
|
416
525
|
<PaceAppLayout
|
|
@@ -421,17 +530,23 @@ describe('PaceAppLayout Component', () => {
|
|
|
421
530
|
</TestWrapper>
|
|
422
531
|
);
|
|
423
532
|
|
|
533
|
+
// Assert - No need to wait for loading since mock returns immediately
|
|
424
534
|
await waitFor(() => {
|
|
425
535
|
expect(screen.getByTestId('custom-fallback')).toBeInTheDocument();
|
|
426
|
-
}
|
|
536
|
+
});
|
|
427
537
|
});
|
|
428
538
|
|
|
429
539
|
it('shows page permission fallback when enforcePagePermissions is true', async () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
540
|
+
// Arrange
|
|
541
|
+
mockUseCan.mockReturnValueOnce({
|
|
542
|
+
can: false,
|
|
543
|
+
isLoading: false,
|
|
544
|
+
error: null,
|
|
545
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
546
|
+
});
|
|
433
547
|
const pageFallback = <div data-testid="page-fallback">Page Access Denied</div>;
|
|
434
548
|
|
|
549
|
+
// Act
|
|
435
550
|
renderWithProviders(
|
|
436
551
|
<TestWrapper>
|
|
437
552
|
<PaceAppLayout
|
|
@@ -443,9 +558,10 @@ describe('PaceAppLayout Component', () => {
|
|
|
443
558
|
</TestWrapper>
|
|
444
559
|
);
|
|
445
560
|
|
|
561
|
+
// Assert - No need to wait for loading since mock returns immediately
|
|
446
562
|
await waitFor(() => {
|
|
447
563
|
expect(screen.getByTestId('page-fallback')).toBeInTheDocument();
|
|
448
|
-
}
|
|
564
|
+
});
|
|
449
565
|
});
|
|
450
566
|
});
|
|
451
567
|
|
|
@@ -514,9 +630,6 @@ describe('PaceAppLayout Component', () => {
|
|
|
514
630
|
|
|
515
631
|
describe('Route-Specific Permissions', () => {
|
|
516
632
|
it('uses route-specific permissions when provided', async () => {
|
|
517
|
-
const { isPermitted } = await import('../../rbac/api');
|
|
518
|
-
vi.mocked(isPermitted).mockResolvedValue(true);
|
|
519
|
-
|
|
520
633
|
renderWithProviders(
|
|
521
634
|
<TestWrapper>
|
|
522
635
|
<PaceAppLayout
|
|
@@ -533,23 +646,18 @@ describe('PaceAppLayout Component', () => {
|
|
|
533
646
|
);
|
|
534
647
|
|
|
535
648
|
await waitFor(() => {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}, { timeout: 3000 });
|
|
547
|
-
});
|
|
649
|
+
// useCan is called with userId, scope, permission, pageId, useCache
|
|
650
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
651
|
+
'user-123',
|
|
652
|
+
expect.objectContaining({ organisationId: 'org-123' }),
|
|
653
|
+
'update:page.dashboard-page',
|
|
654
|
+
'dashboard-page',
|
|
655
|
+
true
|
|
656
|
+
);
|
|
657
|
+
}, { timeout: 5000 });
|
|
658
|
+
}, { timeout: 6000 });
|
|
548
659
|
|
|
549
660
|
it('uses default permission when route not in routePermissions', async () => {
|
|
550
|
-
const { isPermitted } = await import('../../rbac/api');
|
|
551
|
-
vi.mocked(isPermitted).mockResolvedValue(true);
|
|
552
|
-
|
|
553
661
|
renderWithProviders(
|
|
554
662
|
<TestWrapper>
|
|
555
663
|
<PaceAppLayout
|
|
@@ -564,18 +672,17 @@ describe('PaceAppLayout Component', () => {
|
|
|
564
672
|
);
|
|
565
673
|
|
|
566
674
|
await waitFor(() => {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
});
|
|
675
|
+
// useCan is called with userId, scope, permission, pageId, useCache
|
|
676
|
+
// Uses defaultPermission "create" since /dashboard is not in routePermissions
|
|
677
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
678
|
+
'user-123',
|
|
679
|
+
expect.objectContaining({ organisationId: 'org-123' }),
|
|
680
|
+
'create:page.dashboard',
|
|
681
|
+
'dashboard',
|
|
682
|
+
true
|
|
683
|
+
);
|
|
684
|
+
}, { timeout: 5000 });
|
|
685
|
+
}, { timeout: 6000 });
|
|
579
686
|
});
|
|
580
687
|
|
|
581
688
|
describe('Super Admin Bypass', () => {
|
|
@@ -601,11 +708,16 @@ describe('PaceAppLayout Component', () => {
|
|
|
601
708
|
|
|
602
709
|
describe('Callbacks and Event Handling', () => {
|
|
603
710
|
it('calls onPageAccessDenied when access is denied', async () => {
|
|
604
|
-
|
|
605
|
-
vi.mocked(isPermitted).mockResolvedValue(false);
|
|
606
|
-
|
|
711
|
+
// Arrange
|
|
607
712
|
const onPageAccessDenied = vi.fn();
|
|
713
|
+
mockUseCan.mockReturnValueOnce({
|
|
714
|
+
can: false,
|
|
715
|
+
isLoading: false,
|
|
716
|
+
error: null,
|
|
717
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
718
|
+
});
|
|
608
719
|
|
|
720
|
+
// Act
|
|
609
721
|
renderWithProviders(
|
|
610
722
|
<TestWrapper>
|
|
611
723
|
<PaceAppLayout
|
|
@@ -616,17 +728,23 @@ describe('PaceAppLayout Component', () => {
|
|
|
616
728
|
</TestWrapper>
|
|
617
729
|
);
|
|
618
730
|
|
|
731
|
+
// Assert - Callback should be called immediately when can is false
|
|
619
732
|
await waitFor(() => {
|
|
620
733
|
expect(onPageAccessDenied).toHaveBeenCalledWith('dashboard', 'read');
|
|
621
|
-
}
|
|
734
|
+
});
|
|
622
735
|
});
|
|
623
736
|
|
|
624
737
|
it('calls onStrictModeViolation when strict mode is violated', async () => {
|
|
625
|
-
|
|
626
|
-
vi.mocked(isPermitted).mockResolvedValue(false);
|
|
627
|
-
|
|
738
|
+
// Arrange
|
|
628
739
|
const onStrictModeViolation = vi.fn();
|
|
740
|
+
mockUseCan.mockReturnValueOnce({
|
|
741
|
+
can: false,
|
|
742
|
+
isLoading: false,
|
|
743
|
+
error: null,
|
|
744
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
745
|
+
});
|
|
629
746
|
|
|
747
|
+
// Act
|
|
630
748
|
renderWithProviders(
|
|
631
749
|
<TestWrapper>
|
|
632
750
|
<PaceAppLayout
|
|
@@ -638,9 +756,10 @@ describe('PaceAppLayout Component', () => {
|
|
|
638
756
|
</TestWrapper>
|
|
639
757
|
);
|
|
640
758
|
|
|
759
|
+
// Assert - Callback should be called immediately when can is false
|
|
641
760
|
await waitFor(() => {
|
|
642
761
|
expect(onStrictModeViolation).toHaveBeenCalledWith('dashboard', 'read');
|
|
643
|
-
}
|
|
762
|
+
});
|
|
644
763
|
});
|
|
645
764
|
});
|
|
646
765
|
|
|
@@ -714,8 +833,14 @@ describe('PaceAppLayout Component', () => {
|
|
|
714
833
|
// Mock the useUnifiedAuth hook to return null user
|
|
715
834
|
vi.mocked(useUnifiedAuth).mockReturnValue(mockAuthWithoutUser);
|
|
716
835
|
|
|
717
|
-
|
|
718
|
-
|
|
836
|
+
// When there's no user, useCan is called with empty string userId
|
|
837
|
+
// and will return false (no permission)
|
|
838
|
+
mockUseCan.mockReturnValueOnce({
|
|
839
|
+
can: false,
|
|
840
|
+
isLoading: false,
|
|
841
|
+
error: null,
|
|
842
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
843
|
+
});
|
|
719
844
|
|
|
720
845
|
renderWithProviders(
|
|
721
846
|
<TestWrapper>
|
|
@@ -732,6 +857,7 @@ describe('PaceAppLayout Component', () => {
|
|
|
732
857
|
});
|
|
733
858
|
|
|
734
859
|
it('handles missing organisation context', async () => {
|
|
860
|
+
// Arrange
|
|
735
861
|
const mockUserWithoutOrg = {
|
|
736
862
|
...mockUser,
|
|
737
863
|
user_metadata: {},
|
|
@@ -743,12 +869,15 @@ describe('PaceAppLayout Component', () => {
|
|
|
743
869
|
user: mockUserWithoutOrg,
|
|
744
870
|
};
|
|
745
871
|
|
|
746
|
-
// Mock the useUnifiedAuth hook to return user without org context
|
|
747
872
|
vi.mocked(useUnifiedAuth).mockReturnValue(mockAuthWithoutOrg);
|
|
873
|
+
mockUseCan.mockReturnValueOnce({
|
|
874
|
+
can: false,
|
|
875
|
+
isLoading: false,
|
|
876
|
+
error: null,
|
|
877
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
878
|
+
});
|
|
748
879
|
|
|
749
|
-
|
|
750
|
-
vi.mocked(isPermitted).mockResolvedValue(false);
|
|
751
|
-
|
|
880
|
+
// Act
|
|
752
881
|
renderWithProviders(
|
|
753
882
|
<TestWrapper>
|
|
754
883
|
<PaceAppLayout
|
|
@@ -758,9 +887,10 @@ describe('PaceAppLayout Component', () => {
|
|
|
758
887
|
</TestWrapper>
|
|
759
888
|
);
|
|
760
889
|
|
|
890
|
+
// Assert - Component should show access denied
|
|
761
891
|
await waitFor(() => {
|
|
762
|
-
expect(screen.
|
|
763
|
-
}
|
|
892
|
+
expect(screen.getByRole('heading', { name: 'Access Denied' })).toBeInTheDocument();
|
|
893
|
+
});
|
|
764
894
|
});
|
|
765
895
|
});
|
|
766
896
|
|