@jmruthers/pace-core 0.5.73 → 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-INW5YIFV.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-6SYT5WFN.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
- package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
- package/dist/{chunk-2PRPDH66.js → chunk-2CHATWBF.js} +5 -7
- package/dist/chunk-2CHATWBF.js.map +1 -0
- package/dist/{chunk-43C63KLH.js → chunk-2DFZ432F.js} +496 -30
- package/dist/chunk-2DFZ432F.js.map +1 -0
- package/dist/{chunk-M4UMXYNK.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-GBC5PC3N.js → chunk-CY3AHGO4.js} +6256 -1937
- package/dist/chunk-CY3AHGO4.js.map +1 -0
- package/dist/{chunk-BYG6OSTC.js → chunk-DAXLNIDY.js} +48 -50
- 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-LANO5IFV.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-UC2BWIK7.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/implementation-guides/data-tables.md +82 -1
- 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/DataTableCore.tsx +37 -3
- 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/Dialog.tsx +2 -2
- 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/components/Toast/Toast.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- 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-2PRPDH66.js.map +0 -1
- package/dist/chunk-3SP4P7NS.js.map +0 -1
- package/dist/chunk-43C63KLH.js.map +0 -1
- package/dist/chunk-5A4RL4BC.js +0 -5670
- package/dist/chunk-5A4RL4BC.js.map +0 -1
- package/dist/chunk-BYG6OSTC.js.map +0 -1
- package/dist/chunk-CDDYJCYU.js +0 -79
- package/dist/chunk-CDDYJCYU.js.map +0 -1
- package/dist/chunk-F24P24TZ.js +0 -17
- package/dist/chunk-F24P24TZ.js.map +0 -1
- package/dist/chunk-GBC5PC3N.js.map +0 -1
- package/dist/chunk-LANO5IFV.js.map +0 -1
- package/dist/chunk-M4UMXYNK.js.map +0 -1
- package/dist/chunk-RJNE764D.js +0 -953
- package/dist/chunk-RJNE764D.js.map +0 -1
- package/dist/chunk-UC2BWIK7.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-INW5YIFV.js.map → DataTable-HWZQGASI.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-6SYT5WFN.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,824 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file EventProvider Component Tests
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Providers/__tests__
|
|
5
|
-
* @since 0.4.0
|
|
6
|
-
*
|
|
7
|
-
* Comprehensive test suite for EventProvider component covering all critical functionality.
|
|
8
|
-
* Follows testing guidelines with proper structure, naming, and best practices.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import React from 'react';
|
|
12
|
-
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
13
|
-
import userEvent from '@testing-library/user-event';
|
|
14
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
15
|
-
import { EventProvider, useEvents } from '../EventProvider';
|
|
16
|
-
import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
|
|
17
|
-
|
|
18
|
-
// Mock the debug logger
|
|
19
|
-
vi.mock('../../utils/debugLogger', () => ({
|
|
20
|
-
DebugLogger: {
|
|
21
|
-
log: vi.fn(),
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
// Mock the organisation context utility
|
|
26
|
-
vi.mock('../../utils/organisationContext', () => ({
|
|
27
|
-
setOrganisationContext: vi.fn().mockResolvedValue(undefined),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
// Mock UnifiedAuthProvider
|
|
31
|
-
const mockUnifiedAuthState = {
|
|
32
|
-
user: null as any,
|
|
33
|
-
session: null as any,
|
|
34
|
-
supabase: null as any,
|
|
35
|
-
appName: 'test-app',
|
|
36
|
-
setSelectedEventId: vi.fn(),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
vi.mock('../UnifiedAuthProvider', () => ({
|
|
40
|
-
useUnifiedAuth: () => ({
|
|
41
|
-
user: mockUnifiedAuthState.user,
|
|
42
|
-
session: mockUnifiedAuthState.session,
|
|
43
|
-
supabase: mockUnifiedAuthState.supabase,
|
|
44
|
-
appName: mockUnifiedAuthState.appName,
|
|
45
|
-
setSelectedEventId: mockUnifiedAuthState.setSelectedEventId,
|
|
46
|
-
}),
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
// Mock OrganisationProvider
|
|
50
|
-
const mockOrganisationState = {
|
|
51
|
-
selectedOrganisation: null as any,
|
|
52
|
-
ensureOrganisationContext: vi.fn(),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
vi.mock('../OrganisationProvider', () => ({
|
|
56
|
-
useOrganisations: () => ({
|
|
57
|
-
selectedOrganisation: mockOrganisationState.selectedOrganisation,
|
|
58
|
-
ensureOrganisationContext: mockOrganisationState.ensureOrganisationContext,
|
|
59
|
-
}),
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
// Test component that uses the hook
|
|
63
|
-
const TestComponent = () => {
|
|
64
|
-
const { events, isLoading, error, selectedEvent, setSelectedEvent, refreshEvents } = useEvents();
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div data-testid="test-component">
|
|
68
|
-
<div data-testid="events-count">{events.length}</div>
|
|
69
|
-
<div data-testid="is-loading">{isLoading ? 'true' : 'false'}</div>
|
|
70
|
-
<div data-testid="error">{error?.message || 'no-error'}</div>
|
|
71
|
-
<div data-testid="selected-event">{selectedEvent?.event_name || 'no-event'}</div>
|
|
72
|
-
<div data-testid="selected-event-id">{selectedEvent?.event_id || 'no-id'}</div>
|
|
73
|
-
<button onClick={() => setSelectedEvent(events[0] || null)}>
|
|
74
|
-
Select First Event
|
|
75
|
-
</button>
|
|
76
|
-
<button onClick={() => setSelectedEvent(null)}>
|
|
77
|
-
Clear Event
|
|
78
|
-
</button>
|
|
79
|
-
<button onClick={() => refreshEvents()}>
|
|
80
|
-
Refresh Events
|
|
81
|
-
</button>
|
|
82
|
-
</div>
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Wrapper component
|
|
87
|
-
const TestWrapper = ({
|
|
88
|
-
children,
|
|
89
|
-
supabaseClient,
|
|
90
|
-
user = null,
|
|
91
|
-
session = null,
|
|
92
|
-
appName = 'test-app',
|
|
93
|
-
selectedOrganisation = null
|
|
94
|
-
}: {
|
|
95
|
-
children: React.ReactNode;
|
|
96
|
-
supabaseClient?: any;
|
|
97
|
-
user?: any;
|
|
98
|
-
session?: any;
|
|
99
|
-
appName?: string;
|
|
100
|
-
selectedOrganisation?: any;
|
|
101
|
-
}) => {
|
|
102
|
-
// Update mock state
|
|
103
|
-
mockUnifiedAuthState.user = user;
|
|
104
|
-
mockUnifiedAuthState.session = session;
|
|
105
|
-
mockUnifiedAuthState.supabase = supabaseClient;
|
|
106
|
-
mockUnifiedAuthState.appName = appName;
|
|
107
|
-
mockOrganisationState.selectedOrganisation = selectedOrganisation;
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<EventProvider supabaseClient={supabaseClient}>
|
|
111
|
-
{children}
|
|
112
|
-
</EventProvider>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
describe('[component] EventProvider', () => {
|
|
117
|
-
let mockSupabaseClient: any;
|
|
118
|
-
|
|
119
|
-
const mockEvents = [
|
|
120
|
-
{
|
|
121
|
-
event_id: 'event-1',
|
|
122
|
-
event_name: 'Test Event 1',
|
|
123
|
-
event_date: '2024-12-31T18:00:00Z',
|
|
124
|
-
event_venue: 'Test Venue 1',
|
|
125
|
-
event_participants: 100,
|
|
126
|
-
event_colours: { primary: '#ff0000', secondary: '#00ff00' },
|
|
127
|
-
organisation_id: 'org-1',
|
|
128
|
-
is_visible: true,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
event_id: 'event-2',
|
|
132
|
-
event_name: 'Test Event 2',
|
|
133
|
-
event_date: '2025-01-15T19:00:00Z',
|
|
134
|
-
event_venue: 'Test Venue 2',
|
|
135
|
-
event_participants: 150,
|
|
136
|
-
event_colours: { primary: '#0000ff', secondary: '#ffff00' },
|
|
137
|
-
organisation_id: 'org-1',
|
|
138
|
-
is_visible: true,
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
event_id: 'event-3',
|
|
142
|
-
event_name: 'Past Event',
|
|
143
|
-
event_date: '2023-01-01T12:00:00Z',
|
|
144
|
-
event_venue: 'Old Venue',
|
|
145
|
-
event_participants: 50,
|
|
146
|
-
event_colours: { primary: '#888888', secondary: '#cccccc' },
|
|
147
|
-
organisation_id: 'org-1',
|
|
148
|
-
is_visible: true,
|
|
149
|
-
}
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
const mockOrganisation = {
|
|
153
|
-
id: 'org-1',
|
|
154
|
-
name: 'test-org',
|
|
155
|
-
display_name: 'Test Organisation',
|
|
156
|
-
subscription_tier: 'standard',
|
|
157
|
-
settings: {},
|
|
158
|
-
is_active: true,
|
|
159
|
-
parent_id: null,
|
|
160
|
-
created_at: '2023-01-01T00:00:00Z',
|
|
161
|
-
updated_at: '2023-01-01T00:00:00Z'
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
beforeEach(() => {
|
|
165
|
-
vi.clearAllMocks();
|
|
166
|
-
|
|
167
|
-
// Create mock Supabase client
|
|
168
|
-
mockSupabaseClient = createMockSupabaseClient();
|
|
169
|
-
|
|
170
|
-
// Mock RPC function for getting events
|
|
171
|
-
mockSupabaseClient.rpc = vi.fn().mockImplementation((functionName: string, params: any) => {
|
|
172
|
-
if (functionName === 'data_user_events_get') {
|
|
173
|
-
return Promise.resolve({
|
|
174
|
-
data: mockEvents,
|
|
175
|
-
error: null
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return Promise.resolve({ data: null, error: null });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Mock database queries
|
|
182
|
-
mockSupabaseClient.from = vi.fn().mockReturnValue({
|
|
183
|
-
select: vi.fn().mockReturnValue({
|
|
184
|
-
eq: vi.fn().mockReturnValue({
|
|
185
|
-
order: vi.fn().mockResolvedValue({
|
|
186
|
-
data: mockEvents,
|
|
187
|
-
error: null,
|
|
188
|
-
}),
|
|
189
|
-
}),
|
|
190
|
-
}),
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
afterEach(() => {
|
|
195
|
-
vi.restoreAllMocks();
|
|
196
|
-
localStorage.clear();
|
|
197
|
-
sessionStorage.clear();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe('Rendering', () => {
|
|
201
|
-
it('renders with children', () => {
|
|
202
|
-
render(
|
|
203
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
204
|
-
<TestComponent />
|
|
205
|
-
</TestWrapper>
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('renders with default state', () => {
|
|
212
|
-
render(
|
|
213
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
214
|
-
<TestComponent />
|
|
215
|
-
</TestWrapper>
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('0');
|
|
219
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
220
|
-
expect(screen.getByTestId('error')).toHaveTextContent('no-error');
|
|
221
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('renders without supabase client', () => {
|
|
225
|
-
render(
|
|
226
|
-
<TestWrapper>
|
|
227
|
-
<TestComponent />
|
|
228
|
-
</TestWrapper>
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe('Event Loading', () => {
|
|
236
|
-
it('loads events successfully', async () => {
|
|
237
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
238
|
-
const session = testDataGenerators.session();
|
|
239
|
-
|
|
240
|
-
render(
|
|
241
|
-
<TestWrapper
|
|
242
|
-
supabaseClient={mockSupabaseClient}
|
|
243
|
-
user={user}
|
|
244
|
-
session={session}
|
|
245
|
-
selectedOrganisation={mockOrganisation}
|
|
246
|
-
>
|
|
247
|
-
<TestComponent />
|
|
248
|
-
</TestWrapper>
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
await waitFor(() => {
|
|
252
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
253
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
254
|
-
expect(screen.getByTestId('error')).toHaveTextContent('no-error');
|
|
255
|
-
}, { timeout: 3000 });
|
|
256
|
-
}, 10000);
|
|
257
|
-
|
|
258
|
-
it('handles loading errors', async () => {
|
|
259
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
260
|
-
const session = testDataGenerators.session();
|
|
261
|
-
|
|
262
|
-
// Mock RPC error
|
|
263
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
264
|
-
if (functionName === 'data_user_events_get') {
|
|
265
|
-
return Promise.resolve({
|
|
266
|
-
data: null,
|
|
267
|
-
error: new Error('Failed to fetch events')
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
return Promise.resolve({ data: null, error: null });
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
render(
|
|
274
|
-
<TestWrapper
|
|
275
|
-
supabaseClient={mockSupabaseClient}
|
|
276
|
-
user={user}
|
|
277
|
-
session={session}
|
|
278
|
-
selectedOrganisation={mockOrganisation}
|
|
279
|
-
>
|
|
280
|
-
<TestComponent />
|
|
281
|
-
</TestWrapper>
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
await waitFor(() => {
|
|
285
|
-
expect(screen.getByTestId('error')).toHaveTextContent('Failed to fetch events');
|
|
286
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('0');
|
|
287
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
288
|
-
}, { timeout: 5000 });
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('skips loading when missing dependencies', async () => {
|
|
292
|
-
render(
|
|
293
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
294
|
-
<TestComponent />
|
|
295
|
-
</TestWrapper>
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
// Should not load events without user/session/organisation
|
|
299
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('0');
|
|
300
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('handles empty events array', async () => {
|
|
304
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
305
|
-
const session = testDataGenerators.session();
|
|
306
|
-
|
|
307
|
-
// Mock empty events
|
|
308
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
309
|
-
if (functionName === 'data_user_events_get') {
|
|
310
|
-
return Promise.resolve({
|
|
311
|
-
data: [],
|
|
312
|
-
error: null
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
return Promise.resolve({ data: null, error: null });
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
render(
|
|
319
|
-
<TestWrapper
|
|
320
|
-
supabaseClient={mockSupabaseClient}
|
|
321
|
-
user={user}
|
|
322
|
-
session={session}
|
|
323
|
-
selectedOrganisation={mockOrganisation}
|
|
324
|
-
>
|
|
325
|
-
<TestComponent />
|
|
326
|
-
</TestWrapper>
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
await waitFor(() => {
|
|
330
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('0');
|
|
331
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
332
|
-
}, { timeout: 5000 });
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
describe('Event Selection', () => {
|
|
337
|
-
it('auto-selects next event by date', async () => {
|
|
338
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
339
|
-
const session = testDataGenerators.session();
|
|
340
|
-
|
|
341
|
-
render(
|
|
342
|
-
<TestWrapper
|
|
343
|
-
supabaseClient={mockSupabaseClient}
|
|
344
|
-
user={user}
|
|
345
|
-
session={session}
|
|
346
|
-
selectedOrganisation={mockOrganisation}
|
|
347
|
-
>
|
|
348
|
-
<TestComponent />
|
|
349
|
-
</TestWrapper>
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
await waitFor(() => {
|
|
353
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
354
|
-
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
|
|
355
|
-
}, { timeout: 5000 });
|
|
356
|
-
|
|
357
|
-
// Should auto-select the next event (event-1, which is the earliest future event)
|
|
358
|
-
await waitFor(() => {
|
|
359
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
|
|
360
|
-
expect(screen.getByTestId('selected-event-id')).toHaveTextContent('event-1');
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('allows manual event selection', async () => {
|
|
365
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
366
|
-
const session = testDataGenerators.session();
|
|
367
|
-
|
|
368
|
-
render(
|
|
369
|
-
<TestWrapper
|
|
370
|
-
supabaseClient={mockSupabaseClient}
|
|
371
|
-
user={user}
|
|
372
|
-
session={session}
|
|
373
|
-
selectedOrganisation={mockOrganisation}
|
|
374
|
-
>
|
|
375
|
-
<TestComponent />
|
|
376
|
-
</TestWrapper>
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
await waitFor(() => {
|
|
380
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
381
|
-
}, { timeout: 5000 });
|
|
382
|
-
|
|
383
|
-
// Select first event manually
|
|
384
|
-
const selectButton = screen.getByText('Select First Event');
|
|
385
|
-
await userEvent.click(selectButton);
|
|
386
|
-
|
|
387
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
|
|
388
|
-
expect(screen.getByTestId('selected-event-id')).toHaveTextContent('event-1');
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it('allows clearing event selection', async () => {
|
|
392
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
393
|
-
const session = testDataGenerators.session();
|
|
394
|
-
|
|
395
|
-
render(
|
|
396
|
-
<TestWrapper
|
|
397
|
-
supabaseClient={mockSupabaseClient}
|
|
398
|
-
user={user}
|
|
399
|
-
session={session}
|
|
400
|
-
selectedOrganisation={mockOrganisation}
|
|
401
|
-
>
|
|
402
|
-
<TestComponent />
|
|
403
|
-
</TestWrapper>
|
|
404
|
-
);
|
|
405
|
-
|
|
406
|
-
await waitFor(() => {
|
|
407
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
408
|
-
}, { timeout: 5000 });
|
|
409
|
-
|
|
410
|
-
// Clear event selection
|
|
411
|
-
const clearButton = screen.getByText('Clear Event');
|
|
412
|
-
await userEvent.click(clearButton);
|
|
413
|
-
|
|
414
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
|
|
415
|
-
expect(screen.getByTestId('selected-event-id')).toHaveTextContent('no-id');
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('validates event organisation before selection', async () => {
|
|
419
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
420
|
-
const session = testDataGenerators.session();
|
|
421
|
-
|
|
422
|
-
// Create event with different organisation
|
|
423
|
-
const invalidEvent = {
|
|
424
|
-
...mockEvents[0],
|
|
425
|
-
organisation_id: 'different-org'
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
render(
|
|
429
|
-
<TestWrapper
|
|
430
|
-
supabaseClient={mockSupabaseClient}
|
|
431
|
-
user={user}
|
|
432
|
-
session={session}
|
|
433
|
-
selectedOrganisation={mockOrganisation}
|
|
434
|
-
>
|
|
435
|
-
<TestComponent />
|
|
436
|
-
</TestWrapper>
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
await waitFor(() => {
|
|
440
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
441
|
-
}, { timeout: 5000 });
|
|
442
|
-
|
|
443
|
-
// Try to select invalid event
|
|
444
|
-
const TestInvalidSelectionComponent = () => {
|
|
445
|
-
const { setSelectedEvent } = useEvents();
|
|
446
|
-
|
|
447
|
-
const handleInvalidSelection = () => {
|
|
448
|
-
setSelectedEvent(invalidEvent as any);
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
return (
|
|
452
|
-
<button onClick={handleInvalidSelection}>
|
|
453
|
-
Select Invalid Event
|
|
454
|
-
</button>
|
|
455
|
-
);
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
render(
|
|
459
|
-
<TestWrapper
|
|
460
|
-
supabaseClient={mockSupabaseClient}
|
|
461
|
-
user={user}
|
|
462
|
-
session={session}
|
|
463
|
-
selectedOrganisation={mockOrganisation}
|
|
464
|
-
>
|
|
465
|
-
<TestInvalidSelectionComponent />
|
|
466
|
-
</TestWrapper>
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
const invalidButton = screen.getByText('Select Invalid Event');
|
|
470
|
-
await userEvent.click(invalidButton);
|
|
471
|
-
|
|
472
|
-
// Should not select the invalid event
|
|
473
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
describe('Event Persistence', () => {
|
|
478
|
-
it('persists event selection in sessionStorage', async () => {
|
|
479
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
480
|
-
const session = testDataGenerators.session();
|
|
481
|
-
|
|
482
|
-
render(
|
|
483
|
-
<TestWrapper
|
|
484
|
-
supabaseClient={mockSupabaseClient}
|
|
485
|
-
user={user}
|
|
486
|
-
session={session}
|
|
487
|
-
selectedOrganisation={mockOrganisation}
|
|
488
|
-
>
|
|
489
|
-
<TestComponent />
|
|
490
|
-
</TestWrapper>
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
await waitFor(() => {
|
|
494
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
495
|
-
}, { timeout: 5000 });
|
|
496
|
-
|
|
497
|
-
// Select an event
|
|
498
|
-
const selectButton = screen.getByText('Select First Event');
|
|
499
|
-
await userEvent.click(selectButton);
|
|
500
|
-
|
|
501
|
-
// Check that it's persisted
|
|
502
|
-
expect(sessionStorage.getItem('pace-core-selected-event')).toBe('event-1');
|
|
503
|
-
expect(localStorage.getItem('pace-core-selected-event')).toBe('event-1');
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('restores persisted event selection', async () => {
|
|
507
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
508
|
-
const session = testDataGenerators.session();
|
|
509
|
-
|
|
510
|
-
// Set persisted event
|
|
511
|
-
sessionStorage.setItem('pace-core-selected-event', 'event-2');
|
|
512
|
-
|
|
513
|
-
render(
|
|
514
|
-
<TestWrapper
|
|
515
|
-
supabaseClient={mockSupabaseClient}
|
|
516
|
-
user={user}
|
|
517
|
-
session={session}
|
|
518
|
-
selectedOrganisation={mockOrganisation}
|
|
519
|
-
>
|
|
520
|
-
<TestComponent />
|
|
521
|
-
</TestWrapper>
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
await waitFor(() => {
|
|
525
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
526
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 2');
|
|
527
|
-
}, { timeout: 5000 });
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('clears invalid persisted event', async () => {
|
|
531
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
532
|
-
const session = testDataGenerators.session();
|
|
533
|
-
|
|
534
|
-
// Set invalid persisted event
|
|
535
|
-
sessionStorage.setItem('pace-core-selected-event', 'invalid-event-id');
|
|
536
|
-
|
|
537
|
-
render(
|
|
538
|
-
<TestWrapper
|
|
539
|
-
supabaseClient={mockSupabaseClient}
|
|
540
|
-
user={user}
|
|
541
|
-
session={session}
|
|
542
|
-
selectedOrganisation={mockOrganisation}
|
|
543
|
-
>
|
|
544
|
-
<TestComponent />
|
|
545
|
-
</TestWrapper>
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
await waitFor(() => {
|
|
549
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
550
|
-
}, { timeout: 5000 });
|
|
551
|
-
|
|
552
|
-
// Should clear invalid persisted event and auto-select next
|
|
553
|
-
expect(sessionStorage.getItem('pace-core-selected-event')).toBeNull();
|
|
554
|
-
expect(localStorage.getItem('pace-core-selected-event')).toBeNull();
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
describe('Refresh Functionality', () => {
|
|
559
|
-
it('refreshes events', async () => {
|
|
560
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
561
|
-
const session = testDataGenerators.session();
|
|
562
|
-
|
|
563
|
-
render(
|
|
564
|
-
<TestWrapper
|
|
565
|
-
supabaseClient={mockSupabaseClient}
|
|
566
|
-
user={user}
|
|
567
|
-
session={session}
|
|
568
|
-
selectedOrganisation={mockOrganisation}
|
|
569
|
-
>
|
|
570
|
-
<TestComponent />
|
|
571
|
-
</TestWrapper>
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
await waitFor(() => {
|
|
575
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
576
|
-
}, { timeout: 5000 });
|
|
577
|
-
|
|
578
|
-
// Refresh events
|
|
579
|
-
const refreshButton = screen.getByText('Refresh Events');
|
|
580
|
-
await userEvent.click(refreshButton);
|
|
581
|
-
|
|
582
|
-
// Should reload events
|
|
583
|
-
await waitFor(() => {
|
|
584
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
describe('Helper Functions', () => {
|
|
590
|
-
it('correctly identifies next event by date', async () => {
|
|
591
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
592
|
-
const session = testDataGenerators.session();
|
|
593
|
-
|
|
594
|
-
render(
|
|
595
|
-
<TestWrapper
|
|
596
|
-
supabaseClient={mockSupabaseClient}
|
|
597
|
-
user={user}
|
|
598
|
-
session={session}
|
|
599
|
-
selectedOrganisation={mockOrganisation}
|
|
600
|
-
>
|
|
601
|
-
<TestComponent />
|
|
602
|
-
</TestWrapper>
|
|
603
|
-
);
|
|
604
|
-
|
|
605
|
-
await waitFor(() => {
|
|
606
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
607
|
-
}, { timeout: 5000 });
|
|
608
|
-
|
|
609
|
-
// Should select the earliest future event (event-1)
|
|
610
|
-
await waitFor(() => {
|
|
611
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('Test Event 1');
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
it('handles events with no date', async () => {
|
|
616
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
617
|
-
const session = testDataGenerators.session();
|
|
618
|
-
|
|
619
|
-
// Mock events with no date
|
|
620
|
-
const eventsWithoutDate = [
|
|
621
|
-
{
|
|
622
|
-
event_id: 'event-no-date',
|
|
623
|
-
event_name: 'Event Without Date',
|
|
624
|
-
event_date: null,
|
|
625
|
-
event_venue: 'Test Venue',
|
|
626
|
-
event_participants: 100,
|
|
627
|
-
event_colours: { primary: '#ff0000', secondary: '#00ff00' },
|
|
628
|
-
organisation_id: 'org-1',
|
|
629
|
-
is_visible: true,
|
|
630
|
-
}
|
|
631
|
-
];
|
|
632
|
-
|
|
633
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
634
|
-
if (functionName === 'data_user_events_get') {
|
|
635
|
-
return Promise.resolve({
|
|
636
|
-
data: eventsWithoutDate,
|
|
637
|
-
error: null
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
return Promise.resolve({ data: null, error: null });
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
render(
|
|
644
|
-
<TestWrapper
|
|
645
|
-
supabaseClient={mockSupabaseClient}
|
|
646
|
-
user={user}
|
|
647
|
-
session={session}
|
|
648
|
-
selectedOrganisation={mockOrganisation}
|
|
649
|
-
>
|
|
650
|
-
<TestComponent />
|
|
651
|
-
</TestWrapper>
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
await waitFor(() => {
|
|
655
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('1');
|
|
656
|
-
expect(screen.getByTestId('selected-event')).toHaveTextContent('no-event');
|
|
657
|
-
}, { timeout: 5000 });
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
describe('Hook Usage', () => {
|
|
662
|
-
it('provides useEvents hook', () => {
|
|
663
|
-
render(
|
|
664
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
665
|
-
<TestComponent />
|
|
666
|
-
</TestWrapper>
|
|
667
|
-
);
|
|
668
|
-
|
|
669
|
-
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
it('throws error when hook is used outside provider', () => {
|
|
673
|
-
// Suppress console.error for this test
|
|
674
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
675
|
-
|
|
676
|
-
expect(() => {
|
|
677
|
-
render(<TestComponent />);
|
|
678
|
-
}).toThrow('useEvents must be used within an EventProvider');
|
|
679
|
-
|
|
680
|
-
consoleSpy.mockRestore();
|
|
681
|
-
});
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
describe('Error Handling', () => {
|
|
685
|
-
it('handles Supabase client errors gracefully', async () => {
|
|
686
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
687
|
-
const session = testDataGenerators.session();
|
|
688
|
-
|
|
689
|
-
// Mock RPC error
|
|
690
|
-
mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
|
|
691
|
-
if (functionName === 'data_user_events_get') {
|
|
692
|
-
return Promise.reject(new Error('Network error'));
|
|
693
|
-
}
|
|
694
|
-
return Promise.resolve({ data: null, error: null });
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
render(
|
|
698
|
-
<TestWrapper
|
|
699
|
-
supabaseClient={mockSupabaseClient}
|
|
700
|
-
user={user}
|
|
701
|
-
session={session}
|
|
702
|
-
selectedOrganisation={mockOrganisation}
|
|
703
|
-
>
|
|
704
|
-
<TestComponent />
|
|
705
|
-
</TestWrapper>
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
await waitFor(() => {
|
|
709
|
-
expect(screen.getByTestId('error')).toHaveTextContent('Network error');
|
|
710
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('0');
|
|
711
|
-
}, { timeout: 5000 });
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
it('handles missing Supabase client', () => {
|
|
715
|
-
expect(() => {
|
|
716
|
-
render(
|
|
717
|
-
<TestWrapper>
|
|
718
|
-
<TestComponent />
|
|
719
|
-
</TestWrapper>
|
|
720
|
-
);
|
|
721
|
-
}).not.toThrow();
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
it('handles organisation context errors', async () => {
|
|
725
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
726
|
-
const session = testDataGenerators.session();
|
|
727
|
-
|
|
728
|
-
// Mock organisation context error
|
|
729
|
-
mockOrganisationState.ensureOrganisationContext.mockRejectedValue(new Error('Organisation context error'));
|
|
730
|
-
|
|
731
|
-
render(
|
|
732
|
-
<TestWrapper
|
|
733
|
-
supabaseClient={mockSupabaseClient}
|
|
734
|
-
user={user}
|
|
735
|
-
session={session}
|
|
736
|
-
selectedOrganisation={mockOrganisation}
|
|
737
|
-
>
|
|
738
|
-
<TestComponent />
|
|
739
|
-
</TestWrapper>
|
|
740
|
-
);
|
|
741
|
-
|
|
742
|
-
// Should still load events even if organisation context fails
|
|
743
|
-
await waitFor(() => {
|
|
744
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
745
|
-
}, { timeout: 5000 });
|
|
746
|
-
});
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
describe('Context Value Stability', () => {
|
|
750
|
-
it('maintains stable context value references', () => {
|
|
751
|
-
const TestStabilityComponent = () => {
|
|
752
|
-
const { setSelectedEvent, refreshEvents } = useEvents();
|
|
753
|
-
const [renderCount, setRenderCount] = React.useState(0);
|
|
754
|
-
|
|
755
|
-
React.useEffect(() => {
|
|
756
|
-
setRenderCount(prev => prev + 1);
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
return (
|
|
760
|
-
<div>
|
|
761
|
-
<div data-testid="renderCount">{renderCount}</div>
|
|
762
|
-
<div data-testid="hasStableSetSelectedEvent">{typeof setSelectedEvent === 'function' ? 'true' : 'false'}</div>
|
|
763
|
-
<div data-testid="hasStableRefreshEvents">{typeof refreshEvents === 'function' ? 'true' : 'false'}</div>
|
|
764
|
-
</div>
|
|
765
|
-
);
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
render(
|
|
769
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
770
|
-
<TestStabilityComponent />
|
|
771
|
-
</TestWrapper>
|
|
772
|
-
);
|
|
773
|
-
|
|
774
|
-
// Should render multiple times but maintain stable function references
|
|
775
|
-
expect(screen.getByTestId('hasStableSetSelectedEvent')).toHaveTextContent('true');
|
|
776
|
-
expect(screen.getByTestId('hasStableRefreshEvents')).toHaveTextContent('true');
|
|
777
|
-
});
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
describe('Cleanup', () => {
|
|
781
|
-
it('handles component unmount gracefully', () => {
|
|
782
|
-
const { unmount } = render(
|
|
783
|
-
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
784
|
-
<div>Test content</div>
|
|
785
|
-
</TestWrapper>
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
// Should not throw errors on unmount
|
|
789
|
-
expect(() => unmount()).not.toThrow();
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
it('clears persisted data when event is cleared', async () => {
|
|
793
|
-
const user = testDataGenerators.user({ id: 'user-1' });
|
|
794
|
-
const session = testDataGenerators.session();
|
|
795
|
-
|
|
796
|
-
// Set persisted event
|
|
797
|
-
sessionStorage.setItem('pace-core-selected-event', 'event-1');
|
|
798
|
-
localStorage.setItem('pace-core-selected-event', 'event-1');
|
|
799
|
-
|
|
800
|
-
render(
|
|
801
|
-
<TestWrapper
|
|
802
|
-
supabaseClient={mockSupabaseClient}
|
|
803
|
-
user={user}
|
|
804
|
-
session={session}
|
|
805
|
-
selectedOrganisation={mockOrganisation}
|
|
806
|
-
>
|
|
807
|
-
<TestComponent />
|
|
808
|
-
</TestWrapper>
|
|
809
|
-
);
|
|
810
|
-
|
|
811
|
-
await waitFor(() => {
|
|
812
|
-
expect(screen.getByTestId('events-count')).toHaveTextContent('3');
|
|
813
|
-
}, { timeout: 5000 });
|
|
814
|
-
|
|
815
|
-
// Clear event
|
|
816
|
-
const clearButton = screen.getByText('Clear Event');
|
|
817
|
-
await userEvent.click(clearButton);
|
|
818
|
-
|
|
819
|
-
// Persisted data should be cleared
|
|
820
|
-
expect(sessionStorage.getItem('pace-core-selected-event')).toBeNull();
|
|
821
|
-
expect(localStorage.getItem('pace-core-selected-event')).toBeNull();
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
});
|