@jmruthers/pace-core 0.5.129 → 0.5.131
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/{DataTable-O2COE77K.js → DataTable-4TS2QVNU.js} +2 -2
- package/dist/{DataTable-D5cBRca8.d.ts → DataTable-C7GaRZye.d.ts} +3 -1
- package/dist/{chunk-BJ7MCGY6.js → chunk-3BHL6OVZ.js} +57 -42
- package/dist/chunk-3BHL6OVZ.js.map +1 -0
- package/dist/{chunk-MOOJ2TK6.js → chunk-IS4CMSYM.js} +5 -5
- package/dist/chunk-IS4CMSYM.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +2 -2
- package/dist/hooks.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/{types-D4TVpDa1.d.ts → types-D5rqZQXk.d.ts} +42 -1
- package/dist/utils.d.ts +2 -2
- 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 +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- 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 +40 -13
- 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/EventAppRoleData.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/GrantEventAppRoleParams.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 +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- 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/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- 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 +3 -3
- package/package.json +1 -1
- package/src/components/DataTable/DataTable.tsx +11 -0
- package/src/components/DataTable/components/DataTableCore.tsx +49 -40
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +68 -11
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +7 -4
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +25 -7
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +85 -3
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +83 -14
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +77 -6
- package/dist/chunk-BJ7MCGY6.js.map +0 -1
- package/dist/chunk-MOOJ2TK6.js.map +0 -1
- /package/dist/{DataTable-O2COE77K.js.map → DataTable-4TS2QVNU.js.map} +0 -0
|
@@ -295,8 +295,11 @@ describe('PaceAppLayout Security', () => {
|
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
it('prevents access when user lacks permission', async () => {
|
|
298
|
+
// Ensure super admin check completes first
|
|
299
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
300
|
+
|
|
298
301
|
// Mock useCan to return false (deny access)
|
|
299
|
-
mockUseCan.
|
|
302
|
+
mockUseCan.mockReturnValue({
|
|
300
303
|
can: false,
|
|
301
304
|
isLoading: false,
|
|
302
305
|
error: null,
|
|
@@ -309,6 +312,14 @@ describe('PaceAppLayout Security', () => {
|
|
|
309
312
|
</TestWrapper>
|
|
310
313
|
);
|
|
311
314
|
|
|
315
|
+
// Wait for super admin check to complete and component to re-render
|
|
316
|
+
await waitFor(() => {
|
|
317
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
318
|
+
}, { timeout: 1000 });
|
|
319
|
+
|
|
320
|
+
// Wait a bit for the component to process the super admin check result
|
|
321
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
322
|
+
|
|
312
323
|
// Should show access denied when permission is denied
|
|
313
324
|
await waitFor(() => {
|
|
314
325
|
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
@@ -341,8 +352,11 @@ describe('PaceAppLayout Security', () => {
|
|
|
341
352
|
});
|
|
342
353
|
|
|
343
354
|
it('handles permission check failures securely', async () => {
|
|
355
|
+
// Ensure super admin check completes first
|
|
356
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
357
|
+
|
|
344
358
|
// Mock useCan to return an error state
|
|
345
|
-
mockUseCan.
|
|
359
|
+
mockUseCan.mockReturnValue({
|
|
346
360
|
can: false,
|
|
347
361
|
isLoading: false,
|
|
348
362
|
error: new Error('Permission check failed'),
|
|
@@ -355,6 +369,14 @@ describe('PaceAppLayout Security', () => {
|
|
|
355
369
|
</TestWrapper>
|
|
356
370
|
);
|
|
357
371
|
|
|
372
|
+
// Wait for super admin check to complete and component to re-render
|
|
373
|
+
await waitFor(() => {
|
|
374
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
375
|
+
}, { timeout: 1000 });
|
|
376
|
+
|
|
377
|
+
// Wait a bit for the component to process the super admin check result
|
|
378
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
379
|
+
|
|
358
380
|
await waitFor(() => {
|
|
359
381
|
// When permission check throws an error, should show Permission Error page
|
|
360
382
|
expect(screen.getByText('Permission Error')).toBeInTheDocument();
|
|
@@ -674,8 +696,11 @@ describe('PaceAppLayout Security', () => {
|
|
|
674
696
|
});
|
|
675
697
|
|
|
676
698
|
it('handles permission errors securely', async () => {
|
|
699
|
+
// Ensure super admin check completes first
|
|
700
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
701
|
+
|
|
677
702
|
// Mock useCan to return false (deny access)
|
|
678
|
-
mockUseCan.
|
|
703
|
+
mockUseCan.mockReturnValue({
|
|
679
704
|
can: false,
|
|
680
705
|
isLoading: false,
|
|
681
706
|
error: null,
|
|
@@ -688,15 +713,26 @@ describe('PaceAppLayout Security', () => {
|
|
|
688
713
|
</TestWrapper>
|
|
689
714
|
);
|
|
690
715
|
|
|
716
|
+
// Wait for super admin check to complete and component to re-render
|
|
717
|
+
await waitFor(() => {
|
|
718
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
719
|
+
}, { timeout: 1000 });
|
|
720
|
+
|
|
721
|
+
// Wait a bit for the component to process the super admin check result
|
|
722
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
723
|
+
|
|
691
724
|
await waitFor(() => {
|
|
692
725
|
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
693
726
|
}, { timeout: 2000 });
|
|
694
727
|
}, { timeout: 3000 });
|
|
695
728
|
|
|
696
729
|
it('prevents information leakage in error messages', async () => {
|
|
730
|
+
// Ensure super admin check completes first
|
|
731
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
732
|
+
|
|
697
733
|
// Mock useCan to return an error with sensitive information
|
|
698
734
|
const sensitiveError = new Error('Database connection failed: password=secret123');
|
|
699
|
-
mockUseCan.
|
|
735
|
+
mockUseCan.mockReturnValue({
|
|
700
736
|
can: false,
|
|
701
737
|
isLoading: false,
|
|
702
738
|
error: sensitiveError,
|
|
@@ -715,6 +751,14 @@ describe('PaceAppLayout Security', () => {
|
|
|
715
751
|
</TestWrapper>
|
|
716
752
|
);
|
|
717
753
|
|
|
754
|
+
// Wait for super admin check to complete and component to re-render
|
|
755
|
+
await waitFor(() => {
|
|
756
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
757
|
+
}, { timeout: 1000 });
|
|
758
|
+
|
|
759
|
+
// Wait a bit for the component to process the super admin check result
|
|
760
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
761
|
+
|
|
718
762
|
await waitFor(() => {
|
|
719
763
|
expect(screen.getByText('Permission Error')).toBeInTheDocument();
|
|
720
764
|
// Should not expose sensitive information
|
|
@@ -829,19 +873,21 @@ describe('PaceAppLayout Security', () => {
|
|
|
829
873
|
}, { timeout: 3000 });
|
|
830
874
|
|
|
831
875
|
it('does not log strict mode violations for super admins', async () => {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
// Mock super admin status
|
|
876
|
+
// Mock super admin status (resolve immediately)
|
|
835
877
|
mockIsSuperAdmin.mockResolvedValueOnce(true);
|
|
836
878
|
|
|
837
879
|
// Mock useCan to return false (would normally trigger violation)
|
|
838
|
-
mockUseCan.
|
|
880
|
+
mockUseCan.mockReturnValue({
|
|
839
881
|
can: false,
|
|
840
882
|
isLoading: false,
|
|
841
883
|
error: null,
|
|
842
884
|
refetch: vi.fn().mockResolvedValue(undefined),
|
|
843
885
|
});
|
|
844
886
|
|
|
887
|
+
// Set up console spy AFTER mocks are configured but BEFORE render
|
|
888
|
+
// This ensures we catch any violations logged during render
|
|
889
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
890
|
+
|
|
845
891
|
render(
|
|
846
892
|
<TestWrapper>
|
|
847
893
|
<PaceAppLayout
|
|
@@ -855,19 +901,31 @@ describe('PaceAppLayout Security', () => {
|
|
|
855
901
|
</TestWrapper>
|
|
856
902
|
);
|
|
857
903
|
|
|
904
|
+
// Wait for super admin check to complete
|
|
858
905
|
await waitFor(() => {
|
|
859
|
-
// Wait for super admin check to complete
|
|
860
906
|
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
861
|
-
});
|
|
907
|
+
}, { timeout: 1000 });
|
|
908
|
+
|
|
909
|
+
// Wait for component to fully render (super admin should bypass checks)
|
|
910
|
+
await waitFor(() => {
|
|
911
|
+
expect(screen.getByTestId('mock-header')).toBeInTheDocument();
|
|
912
|
+
}, { timeout: 2000 });
|
|
862
913
|
|
|
863
|
-
//
|
|
914
|
+
// Clear any violations that might have been logged before super admin check completed
|
|
915
|
+
consoleSpy.mockClear();
|
|
916
|
+
|
|
917
|
+
// Wait a bit more to ensure all useEffects have run after super admin check
|
|
918
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
919
|
+
|
|
920
|
+
// Should not log strict mode violations for super admins after super admin check completes
|
|
921
|
+
// The violation check should be skipped because isSuperAdminUser is true
|
|
864
922
|
const violationLogs = consoleSpy.mock.calls.filter(call =>
|
|
865
923
|
call[0]?.includes('STRICT MODE VIOLATION')
|
|
866
924
|
);
|
|
867
925
|
expect(violationLogs).toHaveLength(0);
|
|
868
926
|
|
|
869
927
|
consoleSpy.mockRestore();
|
|
870
|
-
}, { timeout:
|
|
928
|
+
}, { timeout: 5000 });
|
|
871
929
|
|
|
872
930
|
it('prevents privilege escalation', async () => {
|
|
873
931
|
// Test that users cannot escalate their privileges
|
|
@@ -883,8 +941,11 @@ describe('PaceAppLayout Security', () => {
|
|
|
883
941
|
// Mock the location to be /admin for this test
|
|
884
942
|
mockLocation.pathname = '/admin';
|
|
885
943
|
|
|
944
|
+
// Ensure super admin check completes first
|
|
945
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
946
|
+
|
|
886
947
|
// Mock useCan to return false (deny admin access)
|
|
887
|
-
mockUseCan.
|
|
948
|
+
mockUseCan.mockReturnValue({
|
|
888
949
|
can: false,
|
|
889
950
|
isLoading: false,
|
|
890
951
|
error: null,
|
|
@@ -903,11 +964,19 @@ describe('PaceAppLayout Security', () => {
|
|
|
903
964
|
</AdminTestWrapper>
|
|
904
965
|
);
|
|
905
966
|
|
|
967
|
+
// Wait for super admin check to complete and component to re-render
|
|
968
|
+
await waitFor(() => {
|
|
969
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
970
|
+
}, { timeout: 1000 });
|
|
971
|
+
|
|
972
|
+
// Wait a bit for the component to process the super admin check result
|
|
973
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
974
|
+
|
|
906
975
|
await waitFor(() => {
|
|
907
976
|
// With privilege escalation prevention, should show access denied for admin
|
|
908
977
|
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
909
978
|
expect(screen.getByText("You don't have permission to access this page.")).toBeInTheDocument();
|
|
910
979
|
}, { timeout: 2000 });
|
|
911
|
-
});
|
|
980
|
+
}, { timeout: 3000 });
|
|
912
981
|
});
|
|
913
982
|
});
|
|
@@ -110,11 +110,13 @@ const mockIsPermitted = vi.fn().mockImplementation((input) => {
|
|
|
110
110
|
return Promise.resolve(true);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
const mockIsSuperAdmin = vi.fn().mockResolvedValue(false);
|
|
114
|
+
|
|
113
115
|
vi.mock('../../../rbac/api', () => ({
|
|
114
116
|
isPermitted: mockIsPermitted,
|
|
115
117
|
getPermissionMap: vi.fn().mockResolvedValue({}),
|
|
116
118
|
getAccessLevel: vi.fn().mockResolvedValue('viewer'),
|
|
117
|
-
isSuperAdmin:
|
|
119
|
+
isSuperAdmin: (...args: any[]) => mockIsSuperAdmin(...args),
|
|
118
120
|
setupRBAC: vi.fn()
|
|
119
121
|
}));
|
|
120
122
|
|
|
@@ -173,6 +175,20 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
|
173
175
|
describe('PaceAppLayout Component', () => {
|
|
174
176
|
let PaceAppLayout: any;
|
|
175
177
|
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
// Reset mocks before each test
|
|
180
|
+
mockUseCan.mockClear();
|
|
181
|
+
mockIsSuperAdmin.mockClear();
|
|
182
|
+
mockIsSuperAdmin.mockResolvedValue(false);
|
|
183
|
+
// Default to allowing access unless test overrides
|
|
184
|
+
mockUseCan.mockReturnValue({
|
|
185
|
+
can: true,
|
|
186
|
+
isLoading: false,
|
|
187
|
+
error: null,
|
|
188
|
+
refetch: vi.fn().mockResolvedValue(undefined),
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
176
192
|
beforeAll(async () => {
|
|
177
193
|
// Set up mocks before importing the component
|
|
178
194
|
vi.doMock('../../Header', () => ({
|
|
@@ -699,8 +715,11 @@ describe('PaceAppLayout Component', () => {
|
|
|
699
715
|
it('shows permission error when checkPermission throws', async () => {
|
|
700
716
|
const mockError = new Error('Permission check failed');
|
|
701
717
|
|
|
718
|
+
// Ensure super admin check completes first (resolve immediately)
|
|
719
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
720
|
+
|
|
702
721
|
// Mock useCan to return an error state
|
|
703
|
-
mockUseCan.
|
|
722
|
+
mockUseCan.mockReturnValue({
|
|
704
723
|
can: false,
|
|
705
724
|
isLoading: false,
|
|
706
725
|
error: mockError,
|
|
@@ -713,6 +732,14 @@ describe('PaceAppLayout Component', () => {
|
|
|
713
732
|
</TestWrapper>
|
|
714
733
|
);
|
|
715
734
|
|
|
735
|
+
// Wait for super admin check to complete and component to re-render
|
|
736
|
+
await waitFor(() => {
|
|
737
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
738
|
+
}, { timeout: 1000 });
|
|
739
|
+
|
|
740
|
+
// Wait a bit for the component to process the super admin check result
|
|
741
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
742
|
+
|
|
716
743
|
await waitFor(() => {
|
|
717
744
|
expect(screen.getByText('Permission Error')).toBeInTheDocument();
|
|
718
745
|
expect(screen.getByText('Permission check failed')).toBeInTheDocument();
|
|
@@ -720,8 +747,11 @@ describe('PaceAppLayout Component', () => {
|
|
|
720
747
|
}, { timeout: 3000 });
|
|
721
748
|
|
|
722
749
|
it('shows access denied when user lacks permission', async () => {
|
|
750
|
+
// Ensure super admin check completes first (resolve immediately)
|
|
751
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
752
|
+
|
|
723
753
|
// Mock useCan to return false (user lacks permission)
|
|
724
|
-
mockUseCan.
|
|
754
|
+
mockUseCan.mockReturnValue({
|
|
725
755
|
can: false,
|
|
726
756
|
isLoading: false,
|
|
727
757
|
error: null,
|
|
@@ -734,6 +764,14 @@ describe('PaceAppLayout Component', () => {
|
|
|
734
764
|
</TestWrapper>
|
|
735
765
|
);
|
|
736
766
|
|
|
767
|
+
// Wait for super admin check to complete and component to re-render
|
|
768
|
+
await waitFor(() => {
|
|
769
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
770
|
+
}, { timeout: 1000 });
|
|
771
|
+
|
|
772
|
+
// Wait a bit for the component to process the super admin check result
|
|
773
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
774
|
+
|
|
737
775
|
await waitFor(() => {
|
|
738
776
|
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
739
777
|
expect(screen.getByText("You don't have permission to access this page.")).toBeInTheDocument();
|
|
@@ -741,8 +779,11 @@ describe('PaceAppLayout Component', () => {
|
|
|
741
779
|
}, { timeout: 3000 });
|
|
742
780
|
|
|
743
781
|
it('shows custom permission fallback when provided', async () => {
|
|
782
|
+
// Ensure super admin check completes first (resolve immediately)
|
|
783
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
784
|
+
|
|
744
785
|
// Mock useCan to return false
|
|
745
|
-
mockUseCan.
|
|
786
|
+
mockUseCan.mockReturnValue({
|
|
746
787
|
can: false,
|
|
747
788
|
isLoading: false,
|
|
748
789
|
error: null,
|
|
@@ -761,14 +802,25 @@ describe('PaceAppLayout Component', () => {
|
|
|
761
802
|
</TestWrapper>
|
|
762
803
|
);
|
|
763
804
|
|
|
805
|
+
// Wait for super admin check to complete and component to re-render
|
|
806
|
+
await waitFor(() => {
|
|
807
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
808
|
+
}, { timeout: 1000 });
|
|
809
|
+
|
|
810
|
+
// Wait a bit for the component to process the super admin check result
|
|
811
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
812
|
+
|
|
764
813
|
await waitFor(() => {
|
|
765
814
|
expect(screen.getByTestId('custom-fallback')).toBeInTheDocument();
|
|
766
815
|
}, { timeout: 2000 });
|
|
767
816
|
}, { timeout: 3000 });
|
|
768
817
|
|
|
769
818
|
it('provides go home button in access denied state', async () => {
|
|
819
|
+
// Ensure super admin check completes first (resolve immediately)
|
|
820
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
821
|
+
|
|
770
822
|
// Mock useCan to return false
|
|
771
|
-
mockUseCan.
|
|
823
|
+
mockUseCan.mockReturnValue({
|
|
772
824
|
can: false,
|
|
773
825
|
isLoading: false,
|
|
774
826
|
error: null,
|
|
@@ -781,6 +833,14 @@ describe('PaceAppLayout Component', () => {
|
|
|
781
833
|
</TestWrapper>
|
|
782
834
|
);
|
|
783
835
|
|
|
836
|
+
// Wait for super admin check to complete and component to re-render
|
|
837
|
+
await waitFor(() => {
|
|
838
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
839
|
+
}, { timeout: 1000 });
|
|
840
|
+
|
|
841
|
+
// Wait a bit for the component to process the super admin check result
|
|
842
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
843
|
+
|
|
784
844
|
await waitFor(() => {
|
|
785
845
|
const goHomeButton = screen.getByText('Go Home');
|
|
786
846
|
expect(goHomeButton).toBeInTheDocument();
|
|
@@ -793,8 +853,11 @@ describe('PaceAppLayout Component', () => {
|
|
|
793
853
|
it('provides go home button in permission error state', async () => {
|
|
794
854
|
const mockError = new Error('Permission check failed');
|
|
795
855
|
|
|
856
|
+
// Ensure super admin check completes first (resolve immediately)
|
|
857
|
+
mockIsSuperAdmin.mockResolvedValueOnce(false);
|
|
858
|
+
|
|
796
859
|
// Mock useCan to return an error state
|
|
797
|
-
mockUseCan.
|
|
860
|
+
mockUseCan.mockReturnValue({
|
|
798
861
|
can: false,
|
|
799
862
|
isLoading: false,
|
|
800
863
|
error: mockError,
|
|
@@ -807,6 +870,14 @@ describe('PaceAppLayout Component', () => {
|
|
|
807
870
|
</TestWrapper>
|
|
808
871
|
);
|
|
809
872
|
|
|
873
|
+
// Wait for super admin check to complete and component to re-render
|
|
874
|
+
await waitFor(() => {
|
|
875
|
+
expect(mockIsSuperAdmin).toHaveBeenCalled();
|
|
876
|
+
}, { timeout: 1000 });
|
|
877
|
+
|
|
878
|
+
// Wait a bit for the component to process the super admin check result
|
|
879
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
880
|
+
|
|
810
881
|
await waitFor(() => {
|
|
811
882
|
const goHomeButton = screen.getByText('Go Home');
|
|
812
883
|
expect(goHomeButton).toBeInTheDocument();
|