@jmruthers/pace-core 0.5.74 → 0.5.75
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-2QR5TER5.js → DataTable-HWZQGASI.js} +8 -8
- package/dist/{PublicLoadingSpinner-DLpF5bbs.d.ts → PublicLoadingSpinner-BKNBT6b6.d.ts} +2 -2
- package/dist/RBACService-C4udt_Zp.d.ts +528 -0
- package/dist/{UnifiedAuthProvider-K4NRGXL4.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
- package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
- package/dist/{chunk-UJMCGBLS.js → chunk-2CHATWBF.js} +5 -7
- package/dist/chunk-2CHATWBF.js.map +1 -0
- package/dist/{chunk-BKVGJVUR.js → chunk-2DFZ432F.js} +496 -30
- package/dist/chunk-2DFZ432F.js.map +1 -0
- package/dist/{chunk-LVQ26TCN.js → chunk-33PHABLB.js} +36 -3
- package/dist/chunk-33PHABLB.js.map +1 -0
- package/dist/chunk-5F3NDPJV.js +232 -0
- package/dist/chunk-5F3NDPJV.js.map +1 -0
- package/dist/chunk-A4FUBC7B.js +17 -0
- package/dist/chunk-A4FUBC7B.js.map +1 -0
- package/dist/{chunk-SMJZMKYN.js → chunk-A6HBIY5P.js} +2 -11
- package/dist/{chunk-SMJZMKYN.js.map → chunk-A6HBIY5P.js.map} +1 -1
- package/dist/{chunk-IHMMNKNA.js → chunk-CY3AHGO4.js} +6256 -1937
- package/dist/chunk-CY3AHGO4.js.map +1 -0
- package/dist/{chunk-H2TNUICK.js → chunk-DAXLNIDY.js} +47 -49
- package/dist/chunk-DAXLNIDY.js.map +1 -0
- package/dist/{chunk-VKOCWWVY.js → chunk-L3RV2ALE.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js.map → chunk-L3RV2ALE.js.map} +1 -1
- package/dist/chunk-LW7MMEAQ.js +59 -0
- package/dist/chunk-LW7MMEAQ.js.map +1 -0
- package/dist/{chunk-DG5Z55HH.js → chunk-NTNILOBC.js} +7 -9
- package/dist/chunk-NTNILOBC.js.map +1 -0
- package/dist/chunk-PYUXFQJ3.js +11 -0
- package/dist/chunk-PYUXFQJ3.js.map +1 -0
- package/dist/chunk-URUTVZ7N.js +27 -0
- package/dist/chunk-URUTVZ7N.js.map +1 -0
- package/dist/chunk-WN6XJWOS.js +2468 -0
- package/dist/chunk-WN6XJWOS.js.map +1 -0
- package/dist/{chunk-3SP4P7NS.js → chunk-XLZ7U46Z.js} +59 -1
- package/dist/chunk-XLZ7U46Z.js.map +1 -0
- package/dist/{chunk-ORSMVXO2.js → chunk-ZTT2AXMX.js} +9 -14
- package/dist/chunk-ZTT2AXMX.js.map +1 -0
- package/dist/components.d.ts +4 -5
- package/dist/components.js +32 -39
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +156 -10
- package/dist/index.js +188 -93
- package/dist/index.js.map +1 -1
- package/dist/{organisation-t-vvQC3g.d.ts → organisation-BtshODVF.d.ts} +4 -3
- package/dist/providers.d.ts +27 -38
- package/dist/providers.js +33 -23
- package/dist/rbac/index.d.ts +61 -5
- package/dist/rbac/index.js +13 -14
- package/dist/styles/index.js +2 -2
- package/dist/theming/runtime.js +1 -3
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/{unified-CMPjE_fv.d.ts → unified-CM7T0aTK.d.ts} +1 -1
- package/dist/useInactivityTracker-MRUU55XI.js +10 -0
- package/dist/useInactivityTracker-MRUU55XI.js.map +1 -0
- package/dist/{usePublicRouteParams-Ua1Vz-HG.d.ts → usePublicRouteParams-B-CumWRc.d.ts} +3 -3
- package/dist/utils.js +7 -9
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +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 +3 -3
- package/docs/api/interfaces/CardProps.md +2 -2
- 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/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/EventLogoProps.md +2 -2
- 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 +2 -2
- 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 +28 -17
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +2 -2
- 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/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +2 -2
- 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/RBACContextType.md +5 -11
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.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 +524 -440
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +14 -14
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +179 -52
- package/docs/architecture/services.md +30 -32
- package/docs/breaking-changes.md +2 -5
- package/docs/migration/service-architecture.md +121 -260
- package/docs/rbac/README-rbac-rls-integration.md +48 -38
- package/{src/rbac/examples → examples/RBAC}/CompleteRBACExample.tsx +3 -2
- package/{src/rbac/examples → examples/RBAC}/EventBasedApp.tsx +5 -4
- package/{src/components/examples → examples/RBAC}/PermissionExample.tsx +7 -6
- package/examples/RBAC/__tests__/PermissionExample.test.tsx +150 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/README.md +37 -0
- package/examples/index.ts +22 -0
- package/{src/examples → examples/public-pages}/CorrectPublicPageImplementation.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicEventPage.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageApp.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageUsageExample.tsx +1 -1
- package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +159 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +22 -18
- package/src/__tests__/TEST_GUIDE_CURSOR.md +650 -9
- package/src/__tests__/helpers/README.md +255 -0
- package/src/__tests__/helpers/index.ts +62 -0
- package/src/__tests__/helpers/supabaseMock.ts +27 -3
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -8
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +55 -0
- package/src/components/DataTable/core/ColumnManager.ts +10 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +254 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +193 -0
- package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +45 -0
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +117 -0
- package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +71 -0
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +122 -0
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/Header/Header.test.tsx +35 -1
- package/src/components/Header/Header.tsx +3 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -3
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +24 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +3 -2
- package/src/hooks/__tests__/useFocusManagement.unit.test.ts +220 -0
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +117 -0
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +295 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +29 -19
- package/src/hooks/__tests__/useRBAC.unit.test.ts +7 -3
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +115 -19
- package/src/hooks/useEventTheme.test.ts +350 -0
- package/src/hooks/useEventTheme.ts +1 -1
- package/src/hooks/useEvents.ts +61 -0
- package/src/hooks/useOrganisationSecurity.test.ts +4 -4
- package/src/hooks/useOrganisationSecurity.ts +2 -2
- package/src/hooks/useOrganisations.ts +64 -0
- package/src/hooks/useSecureDataAccess.test.ts +9 -5
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +18 -3
- package/src/providers/AuthProvider.tsx +8 -292
- package/src/providers/EventProvider.tsx +15 -425
- package/src/providers/InactivityProvider.tsx +8 -231
- package/src/providers/OrganisationProvider.test.simple.tsx +3 -2
- package/src/providers/OrganisationProvider.tsx +11 -890
- package/src/providers/UnifiedAuthProvider.tsx +8 -320
- package/src/providers/__tests__/AuthProvider.test.tsx +18 -17
- package/src/providers/__tests__/EventProvider.test.tsx +253 -2
- package/src/providers/__tests__/InactivityProvider.test-helper.tsx +65 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +46 -114
- package/src/providers/__tests__/OrganisationProvider.test.tsx +313 -3
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +383 -2
- package/src/providers/index.ts +8 -7
- package/src/providers/services/EventServiceProvider.tsx +3 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +3 -0
- package/src/rbac/hooks/usePermissions.test.ts +296 -0
- package/src/rbac/hooks/useRBAC.test.ts +9 -5
- package/src/rbac/hooks/useRBAC.ts +3 -3
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +688 -0
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +507 -0
- package/src/services/AuthService.ts +19 -4
- package/src/services/__tests__/AuthService.test.ts +288 -0
- package/src/styles/core.css +2 -0
- package/src/types/__tests__/guards.test.ts +246 -0
- package/src/types/guards.ts +1 -0
- package/src/types/organisation.ts +3 -2
- package/src/validation/__tests__/sanitization.unit.test.ts +250 -0
- package/src/validation/__tests__/schemaUtils.unit.test.ts +451 -0
- package/src/validation/__tests__/user.unit.test.ts +440 -0
- package/dist/RBACProvider-BO4ilsQB.d.ts +0 -63
- package/dist/UnifiedAuthProvider-D02AMXgO.d.ts +0 -103
- package/dist/chunk-3SP4P7NS.js.map +0 -1
- package/dist/chunk-B5LK25HV.js +0 -953
- package/dist/chunk-B5LK25HV.js.map +0 -1
- package/dist/chunk-BKVGJVUR.js.map +0 -1
- package/dist/chunk-C5Q5LRU5.js +0 -5691
- package/dist/chunk-C5Q5LRU5.js.map +0 -1
- package/dist/chunk-CDDYJCYU.js +0 -79
- package/dist/chunk-CDDYJCYU.js.map +0 -1
- package/dist/chunk-DG5Z55HH.js.map +0 -1
- package/dist/chunk-H2TNUICK.js.map +0 -1
- package/dist/chunk-IHMMNKNA.js.map +0 -1
- package/dist/chunk-LVQ26TCN.js.map +0 -1
- package/dist/chunk-ORSMVXO2.js.map +0 -1
- package/dist/chunk-UJMCGBLS.js.map +0 -1
- package/dist/chunk-V6BHACCH.js +0 -17
- package/dist/chunk-V6BHACCH.js.map +0 -1
- package/dist/rbac/cli/policy-manager.js +0 -278
- package/dist/rbac/cli/policy-manager.js.map +0 -1
- package/docs/api/interfaces/EventContextType.md +0 -96
- package/docs/api/interfaces/EventProviderProps.md +0 -19
- package/src/providers/OrganisationProvider.test.tsx +0 -164
- package/src/providers/UnifiedAuthProvider.test.tsx +0 -124
- package/src/providers/__tests__/AuthProvider.test.tsx.backup +0 -771
- package/src/providers/__tests__/EventProvider.test.tsx.backup +0 -824
- package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +0 -820
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +0 -911
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +0 -166
- package/src/rbac/cli/__tests__/policy-manager.test.ts +0 -339
- package/src/rbac/cli/policy-manager.ts +0 -443
- package/dist/{DataTable-2QR5TER5.js.map → DataTable-HWZQGASI.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-K4NRGXL4.js.map → UnifiedAuthProvider-3NKDOSOK.js.map} +0 -0
- package/dist/{validation-PM_iOaTI.d.ts → validation-D8VcbTzC.d.ts} +2 -2
- /package/src/utils/{appNameResolver.test.ts.backup → appNameResolver.test 2.ts} +0 -0
|
@@ -1,820 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file OrganisationProvider Tests
|
|
3
|
-
* @description Comprehensive tests for OrganisationProvider component
|
|
4
|
-
* @package @jmruthers/pace-core
|
|
5
|
-
* @module Providers/__tests__
|
|
6
|
-
* @since 0.4.0
|
|
7
|
-
*
|
|
8
|
-
* Comprehensive test suite for OrganisationProvider component covering all critical functionality.
|
|
9
|
-
* Follows testing guidelines with proper structure, naming, and best practices.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import React from 'react';
|
|
13
|
-
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
14
|
-
import userEvent from '@testing-library/user-event';
|
|
15
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
16
|
-
import { BrowserRouter } from 'react-router-dom';
|
|
17
|
-
import { OrganisationProvider, useOrganisations } from '../OrganisationProvider';
|
|
18
|
-
import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
|
|
19
|
-
|
|
20
|
-
// Mock the debug logger
|
|
21
|
-
vi.mock('../../utils/debugLogger', () => ({
|
|
22
|
-
DebugLogger: {
|
|
23
|
-
log: vi.fn(),
|
|
24
|
-
},
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
// Mock the organisation context utility
|
|
28
|
-
vi.mock('../../utils/organisationContext', () => ({
|
|
29
|
-
setOrganisationContext: vi.fn().mockResolvedValue(undefined),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
// Mock react-router-dom
|
|
33
|
-
vi.mock('react-router-dom', () => ({
|
|
34
|
-
BrowserRouter: ({ children }: { children: React.ReactNode }) => <div data-testid="browser-router">{children}</div>,
|
|
35
|
-
useNavigate: () => vi.fn(),
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
// Mock UnifiedAuthProvider with comprehensive state management
|
|
39
|
-
const mockUnifiedAuthState = {
|
|
40
|
-
supabaseClient: null as any,
|
|
41
|
-
user: null as any,
|
|
42
|
-
session: null as any,
|
|
43
|
-
signOut: vi.fn(),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
vi.mock('../UnifiedAuthProvider', () => ({
|
|
47
|
-
UnifiedAuthProvider: ({ children, supabaseClient }: { children: React.ReactNode; supabaseClient?: any }) => {
|
|
48
|
-
mockUnifiedAuthState.supabaseClient = supabaseClient;
|
|
49
|
-
return (
|
|
50
|
-
<div data-testid="unified-auth-provider" supabaseclient={supabaseClient} appname="test-app">
|
|
51
|
-
{children}
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
},
|
|
55
|
-
useUnifiedAuth: () => ({
|
|
56
|
-
user: mockUnifiedAuthState.user,
|
|
57
|
-
session: mockUnifiedAuthState.session,
|
|
58
|
-
supabase: mockUnifiedAuthState.supabaseClient,
|
|
59
|
-
signOut: mockUnifiedAuthState.signOut,
|
|
60
|
-
}),
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
// Test component that uses the organisation context
|
|
64
|
-
const TestComponent = () => {
|
|
65
|
-
const org = useOrganisations();
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div>
|
|
69
|
-
<div data-testid="selectedOrg">{org.selectedOrganisation?.display_name || 'No organisation'}</div>
|
|
70
|
-
<div data-testid="isLoading">{org.isLoading ? 'true' : 'false'}</div>
|
|
71
|
-
<div data-testid="error">{org.error?.message || 'No error'}</div>
|
|
72
|
-
<div data-testid="hasValidContext">{org.hasValidOrganisationContext ? 'true' : 'false'}</div>
|
|
73
|
-
<div data-testid="userRole">{org.getUserRole()}</div>
|
|
74
|
-
<div data-testid="isSecure">{org.isOrganisationSecure() ? 'true' : 'false'}</div>
|
|
75
|
-
<div data-testid="organisationsCount">{org.organisations.length}</div>
|
|
76
|
-
<div data-testid="membershipsCount">{org.userMemberships.length}</div>
|
|
77
|
-
<button onClick={() => org.switchOrganisation('org-2')}>
|
|
78
|
-
Switch Organisation
|
|
79
|
-
</button>
|
|
80
|
-
<button onClick={() => org.refreshOrganisations()}>
|
|
81
|
-
Refresh Organisations
|
|
82
|
-
</button>
|
|
83
|
-
<button onClick={() => org.ensureOrganisationContext()}>
|
|
84
|
-
Ensure Context
|
|
85
|
-
</button>
|
|
86
|
-
<button onClick={() => org.validateOrganisationAccess('org-1')}>
|
|
87
|
-
Validate Access
|
|
88
|
-
</button>
|
|
89
|
-
<button onClick={() => org.getPrimaryOrganisation()}>
|
|
90
|
-
Get Primary
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Wrapper component that provides auth context
|
|
97
|
-
const TestWrapper = ({
|
|
98
|
-
children,
|
|
99
|
-
supabaseClient,
|
|
100
|
-
user = null,
|
|
101
|
-
session = null
|
|
102
|
-
}: {
|
|
103
|
-
children: React.ReactNode;
|
|
104
|
-
supabaseClient?: any;
|
|
105
|
-
user?: any;
|
|
106
|
-
session?: any;
|
|
107
|
-
}) => {
|
|
108
|
-
// Update mock state
|
|
109
|
-
mockUnifiedAuthState.user = user;
|
|
110
|
-
mockUnifiedAuthState.session = session;
|
|
111
|
-
mockUnifiedAuthState.supabaseClient = supabaseClient;
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<BrowserRouter>
|
|
115
|
-
<div data-testid="unified-auth-provider" supabaseclient={supabaseClient} appname="test-app">
|
|
116
|
-
<OrganisationProvider>
|
|
117
|
-
{children}
|
|
118
|
-
</OrganisationProvider>
|
|
119
|
-
</div>
|
|
120
|
-
</BrowserRouter>
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
describe('[component] OrganisationProvider', () => {
|
|
125
|
-
let mockSupabaseClient: any;
|
|
126
|
-
let mockQueryBuilder: any;
|
|
127
|
-
|
|
128
|
-
const mockOrganisations = [
|
|
129
|
-
{
|
|
130
|
-
id: '11111111-1111-1111-1111-111111111111',
|
|
131
|
-
name: 'test-org-1',
|
|
132
|
-
display_name: 'Test Organisation 1',
|
|
133
|
-
subscription_tier: 'standard',
|
|
134
|
-
settings: {},
|
|
135
|
-
is_active: true,
|
|
136
|
-
parent_id: null,
|
|
137
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
138
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
id: '22222222-2222-2222-2222-222222222222',
|
|
142
|
-
name: 'test-org-2',
|
|
143
|
-
display_name: 'Test Organisation 2',
|
|
144
|
-
subscription_tier: 'premium',
|
|
145
|
-
settings: {},
|
|
146
|
-
is_active: true,
|
|
147
|
-
parent_id: null,
|
|
148
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
149
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
150
|
-
}
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
const mockMemberships = [
|
|
154
|
-
{
|
|
155
|
-
id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
156
|
-
user_id: 'user-1',
|
|
157
|
-
organisation_id: '11111111-1111-1111-1111-111111111111',
|
|
158
|
-
role: 'org_admin',
|
|
159
|
-
status: 'active',
|
|
160
|
-
granted_at: '2023-01-01T00:00:00Z',
|
|
161
|
-
granted_by: 'admin-1',
|
|
162
|
-
revoked_at: null,
|
|
163
|
-
revoked_by: null,
|
|
164
|
-
notes: null,
|
|
165
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
166
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
id: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
|
170
|
-
user_id: 'user-1',
|
|
171
|
-
organisation_id: '22222222-2222-2222-2222-222222222222',
|
|
172
|
-
role: 'member',
|
|
173
|
-
status: 'active',
|
|
174
|
-
granted_at: '2023-01-01T00:00:00Z',
|
|
175
|
-
granted_by: 'admin-1',
|
|
176
|
-
revoked_at: null,
|
|
177
|
-
revoked_by: null,
|
|
178
|
-
notes: null,
|
|
179
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
180
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
181
|
-
}
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
beforeEach(() => {
|
|
185
|
-
vi.clearAllMocks();
|
|
186
|
-
|
|
187
|
-
// Create mock Supabase client
|
|
188
|
-
mockSupabaseClient = createMockSupabaseClient();
|
|
189
|
-
|
|
190
|
-
// Mock RPC function for getting user organisation roles
|
|
191
|
-
mockSupabaseClient.rpc = vi.fn().mockImplementation((functionName: string, params: any) => {
|
|
192
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
193
|
-
return Promise.resolve({
|
|
194
|
-
data: mockMemberships,
|
|
195
|
-
error: null
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
return Promise.resolve({ data: null, error: null });
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Mock database queries
|
|
202
|
-
mockSupabaseClient.from = vi.fn((table: string) => {
|
|
203
|
-
const createMockQueryBuilder = (data: any) => {
|
|
204
|
-
const queryBuilder = {
|
|
205
|
-
select: vi.fn().mockReturnThis(),
|
|
206
|
-
eq: vi.fn().mockReturnThis(),
|
|
207
|
-
is: vi.fn().mockReturnThis(),
|
|
208
|
-
in: vi.fn().mockReturnThis(),
|
|
209
|
-
order: vi.fn().mockReturnThis(),
|
|
210
|
-
limit: vi.fn().mockReturnThis(),
|
|
211
|
-
single: vi.fn().mockResolvedValue({ data: data[0] || null, error: null }),
|
|
212
|
-
maybeSingle: vi.fn().mockResolvedValue({ data: data[0] || null, error: null }),
|
|
213
|
-
then: vi.fn().mockImplementation((resolve) => {
|
|
214
|
-
resolve({ data, error: null });
|
|
215
|
-
}),
|
|
216
|
-
};
|
|
217
|
-
return queryBuilder;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
if (table === 'rbac_organisation_roles') {
|
|
221
|
-
return createMockQueryBuilder(mockMemberships);
|
|
222
|
-
} else if (table === 'organisations') {
|
|
223
|
-
return createMockQueryBuilder(mockOrganisations);
|
|
224
|
-
}
|
|
225
|
-
return createMockQueryBuilder([]);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Mock auth state
|
|
229
|
-
mockSupabaseClient.auth.getUser = vi.fn().mockResolvedValue({
|
|
230
|
-
data: { user: testDataGenerators.user({ id: 'user-1' }) },
|
|
231
|
-
error: null
|
|
232
|
-
});
|
|
233
|
-
mockSupabaseClient.auth.getSession = vi.fn().mockResolvedValue({
|
|
234
|
-
data: { session: testDataGenerators.session() },
|
|
235
|
-
error: null
|
|
236
|
-
});
|
|
237
|
-
mockSupabaseClient.auth.onAuthStateChange = vi.fn(() => ({
|
|
238
|
-
data: { subscription: { unsubscribe: vi.fn() } }
|
|
239
|
-
}));
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
afterEach(() => {
|
|
243
|
-
vi.restoreAllMocks();
|
|
244
|
-
localStorage.clear();
|
|
245
|
-
sessionStorage.clear();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe('Rendering', () => {
|
|
249
|
-
it('renders children without crashing', () => {
|
|
250
|
-
render(
|
|
251
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
252
|
-
<div>Test content</div>
|
|
253
|
-
</TestWrapper>
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('shows loading state initially', () => {
|
|
260
|
-
render(
|
|
261
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
262
|
-
<TestComponent />
|
|
263
|
-
</TestWrapper>
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('renders without supabase client', () => {
|
|
270
|
-
render(
|
|
271
|
-
<TestWrapper>
|
|
272
|
-
<div>Test content</div>
|
|
273
|
-
</TestWrapper>
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe('Context Hook', () => {
|
|
281
|
-
it('throws error when used outside provider', () => {
|
|
282
|
-
// Suppress console.error for this test
|
|
283
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
284
|
-
|
|
285
|
-
expect(() => {
|
|
286
|
-
render(<TestComponent />);
|
|
287
|
-
}).toThrow('useOrganisations must be used within an OrganisationProvider');
|
|
288
|
-
|
|
289
|
-
consoleSpy.mockRestore();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe('Organisation Loading', () => {
|
|
294
|
-
it('loads organisations successfully', async () => {
|
|
295
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
296
|
-
const session = testDataGenerators.session();
|
|
297
|
-
|
|
298
|
-
render(
|
|
299
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
300
|
-
<TestComponent />
|
|
301
|
-
</TestWrapper>
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
// Should show loading initially
|
|
305
|
-
expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
|
|
306
|
-
|
|
307
|
-
// Wait for loading to complete with shorter timeout
|
|
308
|
-
await waitFor(() => {
|
|
309
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
310
|
-
}, { timeout: 3000 });
|
|
311
|
-
|
|
312
|
-
// Should have loaded organisations
|
|
313
|
-
expect(screen.getByTestId('organisationsCount')).toHaveTextContent('2');
|
|
314
|
-
expect(screen.getByTestId('membershipsCount')).toHaveTextContent('2');
|
|
315
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
316
|
-
expect(screen.getByTestId('hasValidContext')).toHaveTextContent('true');
|
|
317
|
-
}, 10000);
|
|
318
|
-
|
|
319
|
-
it('handles no organisation memberships', async () => {
|
|
320
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
321
|
-
const session = testDataGenerators.session();
|
|
322
|
-
|
|
323
|
-
// Mock empty memberships
|
|
324
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
325
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
326
|
-
return Promise.resolve({
|
|
327
|
-
data: [],
|
|
328
|
-
error: null
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
return Promise.resolve({ data: null, error: null });
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
render(
|
|
335
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
336
|
-
<TestComponent />
|
|
337
|
-
</TestWrapper>
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
await waitFor(() => {
|
|
341
|
-
expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
|
|
342
|
-
}, { timeout: 5000 });
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('handles organisation loading errors', async () => {
|
|
346
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
347
|
-
const session = testDataGenerators.session();
|
|
348
|
-
|
|
349
|
-
// Mock API error
|
|
350
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
351
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
352
|
-
return Promise.resolve({
|
|
353
|
-
data: null,
|
|
354
|
-
error: new Error('Database error')
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
return Promise.resolve({ data: null, error: null });
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
render(
|
|
361
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
362
|
-
<TestComponent />
|
|
363
|
-
</TestWrapper>
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
await waitFor(() => {
|
|
367
|
-
expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
|
|
368
|
-
}, { timeout: 5000 });
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('handles RPC timeout with fallback query', async () => {
|
|
372
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
373
|
-
const session = testDataGenerators.session();
|
|
374
|
-
|
|
375
|
-
// Mock RPC timeout
|
|
376
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
377
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
378
|
-
return Promise.reject(new Error('RPC call timeout after 10 seconds'));
|
|
379
|
-
}
|
|
380
|
-
return Promise.resolve({ data: null, error: null });
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
render(
|
|
384
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
385
|
-
<TestComponent />
|
|
386
|
-
</TestWrapper>
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
await waitFor(() => {
|
|
390
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
391
|
-
}, { timeout: 5000 });
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('handles invalid organisation IDs in memberships', async () => {
|
|
395
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
396
|
-
const session = testDataGenerators.session();
|
|
397
|
-
|
|
398
|
-
// Mock memberships with invalid organisation IDs
|
|
399
|
-
const invalidMemberships = [
|
|
400
|
-
{
|
|
401
|
-
id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
402
|
-
user_id: 'user-1',
|
|
403
|
-
organisation_id: '', // Invalid empty ID
|
|
404
|
-
role: 'org_admin',
|
|
405
|
-
status: 'active',
|
|
406
|
-
granted_at: '2023-01-01T00:00:00Z',
|
|
407
|
-
granted_by: 'admin-1',
|
|
408
|
-
revoked_at: null,
|
|
409
|
-
revoked_by: null,
|
|
410
|
-
notes: null,
|
|
411
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
412
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
413
|
-
}
|
|
414
|
-
];
|
|
415
|
-
|
|
416
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
417
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
418
|
-
return Promise.resolve({
|
|
419
|
-
data: invalidMemberships,
|
|
420
|
-
error: null
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
return Promise.resolve({ data: null, error: null });
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
render(
|
|
427
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
428
|
-
<TestComponent />
|
|
429
|
-
</TestWrapper>
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
await waitFor(() => {
|
|
433
|
-
expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
|
|
434
|
-
}, { timeout: 5000 });
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
describe('Organisation Selection', () => {
|
|
439
|
-
it('auto-selects primary organisation', async () => {
|
|
440
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
441
|
-
const session = testDataGenerators.session();
|
|
442
|
-
|
|
443
|
-
render(
|
|
444
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
445
|
-
<TestComponent />
|
|
446
|
-
</TestWrapper>
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
await waitFor(() => {
|
|
450
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
451
|
-
expect(screen.getByTestId('userRole')).toHaveTextContent('org_admin');
|
|
452
|
-
}, { timeout: 5000 });
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('restores persisted organisation selection', async () => {
|
|
456
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
457
|
-
const session = testDataGenerators.session();
|
|
458
|
-
|
|
459
|
-
// Set persisted organisation
|
|
460
|
-
localStorage.setItem('pace-core-selected-organisation', JSON.stringify(mockOrganisations[1]));
|
|
461
|
-
|
|
462
|
-
render(
|
|
463
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
464
|
-
<TestComponent />
|
|
465
|
-
</TestWrapper>
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
await waitFor(() => {
|
|
469
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 2');
|
|
470
|
-
expect(screen.getByTestId('userRole')).toHaveTextContent('member');
|
|
471
|
-
}, { timeout: 5000 });
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('handles invalid persisted organisation', async () => {
|
|
475
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
476
|
-
const session = testDataGenerators.session();
|
|
477
|
-
|
|
478
|
-
// Set invalid persisted organisation
|
|
479
|
-
localStorage.setItem('pace-core-selected-organisation', JSON.stringify({ id: 'invalid' }));
|
|
480
|
-
|
|
481
|
-
render(
|
|
482
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
483
|
-
<TestComponent />
|
|
484
|
-
</TestWrapper>
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
await waitFor(() => {
|
|
488
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
489
|
-
}, { timeout: 5000 });
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
describe('Organisation Switching', () => {
|
|
494
|
-
it('switches to valid organisation', async () => {
|
|
495
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
496
|
-
const session = testDataGenerators.session();
|
|
497
|
-
|
|
498
|
-
render(
|
|
499
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
500
|
-
<TestComponent />
|
|
501
|
-
</TestWrapper>
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
await waitFor(() => {
|
|
505
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
506
|
-
}, { timeout: 5000 });
|
|
507
|
-
|
|
508
|
-
// Switch to second organisation
|
|
509
|
-
const switchButton = screen.getByText('Switch Organisation');
|
|
510
|
-
await userEvent.click(switchButton);
|
|
511
|
-
|
|
512
|
-
await waitFor(() => {
|
|
513
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 2');
|
|
514
|
-
expect(screen.getByTestId('userRole')).toHaveTextContent('member');
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
it('handles switching to invalid organisation', async () => {
|
|
519
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
520
|
-
const session = testDataGenerators.session();
|
|
521
|
-
|
|
522
|
-
render(
|
|
523
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
524
|
-
<TestComponent />
|
|
525
|
-
</TestWrapper>
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
await waitFor(() => {
|
|
529
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
530
|
-
}, { timeout: 5000 });
|
|
531
|
-
|
|
532
|
-
// Try to switch to invalid organisation
|
|
533
|
-
const TestInvalidSwitchComponent = () => {
|
|
534
|
-
const org = useOrganisations();
|
|
535
|
-
|
|
536
|
-
const handleInvalidSwitch = async () => {
|
|
537
|
-
try {
|
|
538
|
-
await org.switchOrganisation('invalid-org-id');
|
|
539
|
-
} catch (error) {
|
|
540
|
-
// Error should be thrown
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
return (
|
|
545
|
-
<button onClick={handleInvalidSwitch}>
|
|
546
|
-
Switch to Invalid
|
|
547
|
-
</button>
|
|
548
|
-
);
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
render(
|
|
552
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
553
|
-
<TestInvalidSwitchComponent />
|
|
554
|
-
</TestWrapper>
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
const invalidSwitchButton = screen.getByText('Switch to Invalid');
|
|
558
|
-
await userEvent.click(invalidSwitchButton);
|
|
559
|
-
|
|
560
|
-
// Should not change organisation
|
|
561
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
describe('Security Functions', () => {
|
|
566
|
-
it('validates organisation access', async () => {
|
|
567
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
568
|
-
const session = testDataGenerators.session();
|
|
569
|
-
|
|
570
|
-
render(
|
|
571
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
572
|
-
<TestComponent />
|
|
573
|
-
</TestWrapper>
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
await waitFor(() => {
|
|
577
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
578
|
-
}, { timeout: 5000 });
|
|
579
|
-
|
|
580
|
-
const validateButton = screen.getByText('Validate Access');
|
|
581
|
-
await userEvent.click(validateButton);
|
|
582
|
-
|
|
583
|
-
// Should not throw error for valid organisation
|
|
584
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
it('ensures organisation context', async () => {
|
|
588
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
589
|
-
const session = testDataGenerators.session();
|
|
590
|
-
|
|
591
|
-
render(
|
|
592
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
593
|
-
<TestComponent />
|
|
594
|
-
</TestWrapper>
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
await waitFor(() => {
|
|
598
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
599
|
-
}, { timeout: 5000 });
|
|
600
|
-
|
|
601
|
-
const ensureButton = screen.getByText('Ensure Context');
|
|
602
|
-
await userEvent.click(ensureButton);
|
|
603
|
-
|
|
604
|
-
// Should not throw error when context is available
|
|
605
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
it('checks organisation security status', async () => {
|
|
609
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
610
|
-
const session = testDataGenerators.session();
|
|
611
|
-
|
|
612
|
-
render(
|
|
613
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
614
|
-
<TestComponent />
|
|
615
|
-
</TestWrapper>
|
|
616
|
-
);
|
|
617
|
-
|
|
618
|
-
await waitFor(() => {
|
|
619
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
620
|
-
}, { timeout: 5000 });
|
|
621
|
-
|
|
622
|
-
expect(screen.getByTestId('isSecure')).toHaveTextContent('true');
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it('gets primary organisation', async () => {
|
|
626
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
627
|
-
const session = testDataGenerators.session();
|
|
628
|
-
|
|
629
|
-
render(
|
|
630
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
631
|
-
<TestComponent />
|
|
632
|
-
</TestWrapper>
|
|
633
|
-
);
|
|
634
|
-
|
|
635
|
-
await waitFor(() => {
|
|
636
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
637
|
-
}, { timeout: 5000 });
|
|
638
|
-
|
|
639
|
-
const primaryButton = screen.getByText('Get Primary');
|
|
640
|
-
await userEvent.click(primaryButton);
|
|
641
|
-
|
|
642
|
-
// Should not throw error
|
|
643
|
-
expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
|
|
644
|
-
});
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
describe('Refresh Functionality', () => {
|
|
648
|
-
it('refreshes organisations', async () => {
|
|
649
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
650
|
-
const session = testDataGenerators.session();
|
|
651
|
-
|
|
652
|
-
render(
|
|
653
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
654
|
-
<TestComponent />
|
|
655
|
-
</TestWrapper>
|
|
656
|
-
);
|
|
657
|
-
|
|
658
|
-
await waitFor(() => {
|
|
659
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
660
|
-
}, { timeout: 5000 });
|
|
661
|
-
|
|
662
|
-
const refreshButton = screen.getByText('Refresh Organisations');
|
|
663
|
-
await userEvent.click(refreshButton);
|
|
664
|
-
|
|
665
|
-
// Should show loading state during refresh
|
|
666
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('true');
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
describe('Error States', () => {
|
|
671
|
-
it('shows error when no user', () => {
|
|
672
|
-
render(
|
|
673
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
674
|
-
<TestComponent />
|
|
675
|
-
</TestWrapper>
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
// Should show loading initially, then error state
|
|
679
|
-
expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
it('shows error when no session', () => {
|
|
683
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
684
|
-
|
|
685
|
-
render(
|
|
686
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user}>
|
|
687
|
-
<TestComponent />
|
|
688
|
-
</TestWrapper>
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
// Should show loading initially, then error state
|
|
692
|
-
expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it('shows error when no supabase client', () => {
|
|
696
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
697
|
-
const session = testDataGenerators.session();
|
|
698
|
-
|
|
699
|
-
render(
|
|
700
|
-
<TestWrapper user={user} session={session}>
|
|
701
|
-
<TestComponent />
|
|
702
|
-
</TestWrapper>
|
|
703
|
-
);
|
|
704
|
-
|
|
705
|
-
// Should show loading initially, then error state
|
|
706
|
-
expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
describe('Context Values', () => {
|
|
711
|
-
it('provides all required context values', async () => {
|
|
712
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
713
|
-
const session = testDataGenerators.session();
|
|
714
|
-
|
|
715
|
-
render(
|
|
716
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
717
|
-
<TestComponent />
|
|
718
|
-
</TestWrapper>
|
|
719
|
-
);
|
|
720
|
-
|
|
721
|
-
await waitFor(() => {
|
|
722
|
-
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
723
|
-
}, { timeout: 5000 });
|
|
724
|
-
|
|
725
|
-
// Check all context values are available
|
|
726
|
-
expect(screen.getByTestId('selectedOrg')).toBeInTheDocument();
|
|
727
|
-
expect(screen.getByTestId('isLoading')).toBeInTheDocument();
|
|
728
|
-
expect(screen.getByTestId('error')).toBeInTheDocument();
|
|
729
|
-
expect(screen.getByTestId('hasValidContext')).toBeInTheDocument();
|
|
730
|
-
expect(screen.getByTestId('userRole')).toBeInTheDocument();
|
|
731
|
-
expect(screen.getByTestId('isSecure')).toBeInTheDocument();
|
|
732
|
-
expect(screen.getByTestId('organisationsCount')).toBeInTheDocument();
|
|
733
|
-
expect(screen.getByTestId('membershipsCount')).toBeInTheDocument();
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
it('provides placeholder context when no organisation', () => {
|
|
737
|
-
const TestPlaceholderComponent = () => {
|
|
738
|
-
const org = useOrganisations();
|
|
739
|
-
|
|
740
|
-
return (
|
|
741
|
-
<div>
|
|
742
|
-
<div data-testid="hasPlaceholderOrg">{org.selectedOrganisation ? 'true' : 'false'}</div>
|
|
743
|
-
<div data-testid="hasValidContext">{org.hasValidOrganisationContext ? 'true' : 'false'}</div>
|
|
744
|
-
</div>
|
|
745
|
-
);
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
render(
|
|
749
|
-
<TestWrapper>
|
|
750
|
-
<TestPlaceholderComponent />
|
|
751
|
-
</TestWrapper>
|
|
752
|
-
);
|
|
753
|
-
|
|
754
|
-
// Should show placeholder values
|
|
755
|
-
expect(screen.getByTestId('hasPlaceholderOrg')).toHaveTextContent('true');
|
|
756
|
-
expect(screen.getByTestId('hasValidContext')).toHaveTextContent('false');
|
|
757
|
-
});
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
describe('Cleanup', () => {
|
|
761
|
-
it('handles component unmount gracefully', () => {
|
|
762
|
-
const { unmount } = render(
|
|
763
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
764
|
-
<div>Test content</div>
|
|
765
|
-
</TestWrapper>
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
// Should not throw errors on unmount
|
|
769
|
-
expect(() => unmount()).not.toThrow();
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
it('clears cached data on unmount', () => {
|
|
773
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
774
|
-
const session = testDataGenerators.session();
|
|
775
|
-
|
|
776
|
-
// Set some cached data
|
|
777
|
-
localStorage.setItem('pace-core-selected-organisation', 'test-data');
|
|
778
|
-
|
|
779
|
-
const { unmount } = render(
|
|
780
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
781
|
-
<div>Test content</div>
|
|
782
|
-
</TestWrapper>
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
unmount();
|
|
786
|
-
|
|
787
|
-
// Cached data should be cleared
|
|
788
|
-
expect(localStorage.getItem('pace-core-selected-organisation')).toBeNull();
|
|
789
|
-
});
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
describe('Retry Logic', () => {
|
|
793
|
-
it('handles retry count limits', async () => {
|
|
794
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
795
|
-
const session = testDataGenerators.session();
|
|
796
|
-
|
|
797
|
-
// Mock consistent failures
|
|
798
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
799
|
-
if (functionName === 'data_user_organisation_roles_get') {
|
|
800
|
-
return Promise.resolve({
|
|
801
|
-
data: null,
|
|
802
|
-
error: new Error('Consistent failure')
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
return Promise.resolve({ data: null, error: null });
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
render(
|
|
809
|
-
<TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
|
|
810
|
-
<TestComponent />
|
|
811
|
-
</TestWrapper>
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
// Should eventually show error state after retries
|
|
815
|
-
await waitFor(() => {
|
|
816
|
-
expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
|
|
817
|
-
}, { timeout: 10000 });
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
});
|