@jmruthers/pace-core 0.5.190 → 0.5.191
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-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{DataTable-ON3IXISJ.js → DataTable-WKRZD47S.js} +6 -6
- package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +1 -1
- package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-FTSG5XH7.js} +3 -3
- package/dist/{api-I6UCQ5S6.js → api-IHKALJZD.js} +2 -2
- package/dist/{chunk-J2XXC7R5.js → chunk-6LTQQAT6.js} +77 -111
- package/dist/chunk-6LTQQAT6.js.map +1 -0
- package/dist/{chunk-STYK4OH2.js → chunk-6TQDD426.js} +10 -10
- package/dist/chunk-6TQDD426.js.map +1 -0
- package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
- package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
- package/dist/{chunk-73HSNNOQ.js → chunk-LOMZXPSN.js} +13 -13
- package/dist/{chunk-Y4BUBBHD.js → chunk-OETXORNB.js} +3 -3
- package/dist/{chunk-RUYZKXOD.js → chunk-ROXMHMY2.js} +5 -3
- package/dist/chunk-ROXMHMY2.js.map +1 -0
- package/dist/{chunk-SDMHPX3X.js → chunk-ULHIJK66.js} +56 -21
- package/dist/{chunk-SDMHPX3X.js.map → chunk-ULHIJK66.js.map} +1 -1
- package/dist/{chunk-VVBAW5A5.js → chunk-VKB2CO4Z.js} +46 -35
- package/dist/chunk-VKB2CO4Z.js.map +1 -0
- package/dist/{chunk-HQVPB5MZ.js → chunk-VRGWKHDB.js} +6 -6
- package/dist/{chunk-NIU6J6OX.js → chunk-XNYQOL3Z.js} +16 -16
- package/dist/chunk-XNYQOL3Z.js.map +1 -0
- package/dist/{chunk-4QYC5L4K.js → chunk-XYXSXPUK.js} +22 -27
- package/dist/chunk-XYXSXPUK.js.map +1 -0
- package/dist/components.d.ts +3 -3
- package/dist/components.js +8 -8
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/hooks.d.ts +12 -12
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +18 -23
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +6 -6
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +2 -2
- 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/Logger.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/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +16 -16
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +767 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/package.json +1 -1
- package/src/__tests__/public-recipe-view.test.ts +10 -10
- package/src/__tests__/rls-policies.test.ts +13 -13
- package/src/components/AddressField/README.md +6 -6
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -15
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +60 -15
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +9 -8
- package/src/hooks/public/usePublicEvent.ts +8 -8
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/useFileDisplay.ts +8 -9
- package/src/hooks/useQueryCache.ts +6 -6
- package/src/hooks/useSecureDataAccess.test.ts +8 -8
- package/src/hooks/useSecureDataAccess.ts +15 -11
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
- package/src/rbac/utils/eventContext.ts +5 -2
- package/src/services/AuthService.ts +37 -8
- package/src/services/OrganisationService.ts +92 -139
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/file-reference/index.ts +4 -4
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/dist/chunk-4QYC5L4K.js.map +0 -1
- package/dist/chunk-J2XXC7R5.js.map +0 -1
- package/dist/chunk-NIU6J6OX.js.map +0 -1
- package/dist/chunk-RUYZKXOD.js.map +0 -1
- package/dist/chunk-STYK4OH2.js.map +0 -1
- package/dist/chunk-VVBAW5A5.js.map +0 -1
- /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-WKRZD47S.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
- /package/dist/{api-I6UCQ5S6.js.map → api-IHKALJZD.js.map} +0 -0
- /package/dist/{chunk-73HSNNOQ.js.map → chunk-LOMZXPSN.js.map} +0 -0
- /package/dist/{chunk-Y4BUBBHD.js.map → chunk-OETXORNB.js.map} +0 -0
- /package/dist/{chunk-HQVPB5MZ.js.map → chunk-VRGWKHDB.js.map} +0 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { usePublicEvent, clearPublicEventCache, getPublicEventCacheStats } from '../public/usePublicEvent';
|
|
5
|
+
import { usePublicPageContext } from '../../components/PublicLayout/PublicPageProvider';
|
|
6
|
+
|
|
7
|
+
// Mock the PublicPageProvider
|
|
8
|
+
vi.mock('../../components/PublicLayout/PublicPageProvider', () => ({
|
|
9
|
+
usePublicPageContext: vi.fn(() => ({
|
|
10
|
+
environment: {
|
|
11
|
+
supabaseUrl: 'https://test.supabase.co',
|
|
12
|
+
supabaseKey: 'test-anon-key'
|
|
13
|
+
}
|
|
14
|
+
}))
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock Supabase client
|
|
18
|
+
const mockSupabaseClient = {
|
|
19
|
+
rpc: vi.fn(),
|
|
20
|
+
from: vi.fn(() => ({
|
|
21
|
+
select: vi.fn(() => ({
|
|
22
|
+
eq: vi.fn(() => ({
|
|
23
|
+
eq: vi.fn(() => ({
|
|
24
|
+
not: vi.fn(() => ({
|
|
25
|
+
limit: vi.fn(() => ({
|
|
26
|
+
single: vi.fn()
|
|
27
|
+
}))
|
|
28
|
+
}))
|
|
29
|
+
}))
|
|
30
|
+
}))
|
|
31
|
+
}))
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Mock createClient
|
|
36
|
+
vi.mock('@supabase/supabase-js', () => ({
|
|
37
|
+
createClient: vi.fn(() => mockSupabaseClient)
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Mock environment variables
|
|
41
|
+
const originalEnv = import.meta.env;
|
|
42
|
+
|
|
43
|
+
describe('usePublicEvent - Simple Tests', () => {
|
|
44
|
+
let originalMode: string | undefined;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// Ensure logger is enabled by setting MODE to development
|
|
48
|
+
originalMode = import.meta.env.MODE;
|
|
49
|
+
(import.meta.env as any).MODE = 'development';
|
|
50
|
+
|
|
51
|
+
import('../../utils/core/logger').then(({ Logger, LogLevel }) => {
|
|
52
|
+
Logger.configure({
|
|
53
|
+
level: LogLevel.DEBUG,
|
|
54
|
+
includeTimestamp: false,
|
|
55
|
+
includeComponent: true,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
clearPublicEventCache();
|
|
61
|
+
|
|
62
|
+
// Reset environment
|
|
63
|
+
Object.defineProperty(import.meta, 'env', {
|
|
64
|
+
value: {
|
|
65
|
+
VITE_SUPABASE_URL: 'https://test.supabase.co',
|
|
66
|
+
VITE_SUPABASE_ANON_KEY: 'test-anon-key'
|
|
67
|
+
},
|
|
68
|
+
writable: true
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Mock window object
|
|
72
|
+
Object.defineProperty(window, 'location', {
|
|
73
|
+
value: { href: 'https://test.com' },
|
|
74
|
+
writable: true
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
// Restore original mode
|
|
80
|
+
if (originalMode !== undefined) {
|
|
81
|
+
(import.meta.env as any).MODE = originalMode;
|
|
82
|
+
}
|
|
83
|
+
vi.clearAllMocks();
|
|
84
|
+
clearPublicEventCache();
|
|
85
|
+
Object.defineProperty(import.meta, 'env', {
|
|
86
|
+
value: originalEnv,
|
|
87
|
+
writable: true
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Basic Functionality', () => {
|
|
92
|
+
it('should initialize with loading state', () => {
|
|
93
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
94
|
+
|
|
95
|
+
expect(result.current.isLoading).toBe(true);
|
|
96
|
+
expect(result.current.event).toBe(null);
|
|
97
|
+
expect(result.current.error).toBe(null);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should fetch event data successfully via RPC', async () => {
|
|
101
|
+
const mockEventData = {
|
|
102
|
+
event_id: '123',
|
|
103
|
+
event_name: 'Test Event',
|
|
104
|
+
event_date: '2024-01-01',
|
|
105
|
+
event_venue: 'Test Venue',
|
|
106
|
+
event_participants: 100,
|
|
107
|
+
event_colours: { primary: '#000000' },
|
|
108
|
+
organisation_id: 'org-123',
|
|
109
|
+
event_days: 1,
|
|
110
|
+
event_typicalunit: 'km',
|
|
111
|
+
event_rounddown: false,
|
|
112
|
+
event_youthmultiplier: 1.0,
|
|
113
|
+
event_catering_email: 'test@example.com',
|
|
114
|
+
event_news: 'Test news',
|
|
115
|
+
event_billing: 'Test billing',
|
|
116
|
+
event_footer: 'Test footer',
|
|
117
|
+
event_email: 'event@example.com',
|
|
118
|
+
event_logo: null
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
122
|
+
data: [mockEventData],
|
|
123
|
+
error: null
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(result.current.isLoading).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.current.event).toEqual({
|
|
133
|
+
id: '123',
|
|
134
|
+
event_id: '123',
|
|
135
|
+
event_name: 'Test Event',
|
|
136
|
+
event_code: 'test-event',
|
|
137
|
+
event_date: '2024-01-01',
|
|
138
|
+
event_venue: 'Test Venue',
|
|
139
|
+
event_participants: 100,
|
|
140
|
+
event_logo: null,
|
|
141
|
+
event_colours: { primary: '#000000' },
|
|
142
|
+
organisation_id: 'org-123',
|
|
143
|
+
is_visible: true,
|
|
144
|
+
created_at: expect.any(String),
|
|
145
|
+
updated_at: expect.any(String)
|
|
146
|
+
});
|
|
147
|
+
expect(result.current.error).toBe(null);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle event not found', async () => {
|
|
151
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
152
|
+
data: [],
|
|
153
|
+
error: null
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const { result } = renderHook(() => usePublicEvent('nonexistent-event'));
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(result.current.isLoading).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result.current.event).toBe(null);
|
|
163
|
+
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should handle invalid event code', async () => {
|
|
167
|
+
const { result } = renderHook(() => usePublicEvent(''));
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(result.current.isLoading).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result.current.event).toBe(null);
|
|
174
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
175
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Caching', () => {
|
|
180
|
+
it('should cache event data', async () => {
|
|
181
|
+
const mockEventData = {
|
|
182
|
+
event_id: '123',
|
|
183
|
+
event_name: 'Test Event',
|
|
184
|
+
event_date: '2024-01-01',
|
|
185
|
+
event_venue: 'Test Venue',
|
|
186
|
+
event_participants: 100,
|
|
187
|
+
event_colours: { primary: '#000000' },
|
|
188
|
+
organisation_id: 'org-123',
|
|
189
|
+
event_days: 1,
|
|
190
|
+
event_typicalunit: 'km',
|
|
191
|
+
event_rounddown: false,
|
|
192
|
+
event_youthmultiplier: 1.0,
|
|
193
|
+
event_catering_email: 'test@example.com',
|
|
194
|
+
event_news: 'Test news',
|
|
195
|
+
event_billing: 'Test billing',
|
|
196
|
+
event_footer: 'Test footer',
|
|
197
|
+
event_email: 'event@example.com',
|
|
198
|
+
event_logo: null
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
202
|
+
data: [mockEventData],
|
|
203
|
+
error: null
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const { result, rerender } = renderHook(() => usePublicEvent('test-event'));
|
|
207
|
+
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(result.current.isLoading).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Rerender with same event code - should use cache
|
|
213
|
+
rerender();
|
|
214
|
+
|
|
215
|
+
// Should not call RPC again
|
|
216
|
+
expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('Provider Context Integration', () => {
|
|
221
|
+
it('should use PublicPageContext when available', async () => {
|
|
222
|
+
const mockContext = {
|
|
223
|
+
environment: {
|
|
224
|
+
supabaseUrl: 'https://context.supabase.co',
|
|
225
|
+
supabaseKey: 'context-anon-key'
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
vi.mocked(usePublicPageContext).mockReturnValue(mockContext);
|
|
230
|
+
|
|
231
|
+
const mockEventData = {
|
|
232
|
+
event_id: '123',
|
|
233
|
+
event_name: 'Test Event',
|
|
234
|
+
event_date: '2024-01-01',
|
|
235
|
+
event_venue: 'Test Venue',
|
|
236
|
+
event_participants: 100,
|
|
237
|
+
event_colours: { primary: '#000000' },
|
|
238
|
+
organisation_id: 'org-123',
|
|
239
|
+
event_days: 1,
|
|
240
|
+
event_typicalunit: 'km',
|
|
241
|
+
event_rounddown: false,
|
|
242
|
+
event_youthmultiplier: 1.0,
|
|
243
|
+
event_catering_email: 'test@example.com',
|
|
244
|
+
event_news: 'Test news',
|
|
245
|
+
event_billing: 'Test billing',
|
|
246
|
+
event_footer: 'Test footer',
|
|
247
|
+
event_email: 'event@example.com',
|
|
248
|
+
event_logo: null
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
252
|
+
data: [mockEventData],
|
|
253
|
+
error: null
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
257
|
+
|
|
258
|
+
await waitFor(() => {
|
|
259
|
+
expect(result.current.isLoading).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.current.event).toBeTruthy();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Cache Management Utilities', () => {
|
|
267
|
+
it('should clear public event cache', () => {
|
|
268
|
+
// Add some data to cache
|
|
269
|
+
const stats = getPublicEventCacheStats();
|
|
270
|
+
expect(stats.size).toBe(0);
|
|
271
|
+
|
|
272
|
+
// Clear cache
|
|
273
|
+
clearPublicEventCache();
|
|
274
|
+
|
|
275
|
+
const statsAfter = getPublicEventCacheStats();
|
|
276
|
+
expect(statsAfter.size).toBe(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should get cache statistics', () => {
|
|
280
|
+
const stats = getPublicEventCacheStats();
|
|
281
|
+
expect(stats).toEqual({
|
|
282
|
+
size: 0,
|
|
283
|
+
keys: []
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('Refetch Functionality', () => {
|
|
289
|
+
it('refetches data when refetch is called', async () => {
|
|
290
|
+
const mockEventData = {
|
|
291
|
+
event_id: '123',
|
|
292
|
+
event_name: 'Test Event',
|
|
293
|
+
event_date: '2024-01-01',
|
|
294
|
+
event_venue: 'Test Venue',
|
|
295
|
+
event_participants: 100,
|
|
296
|
+
event_colours: { primary: '#000000' },
|
|
297
|
+
organisation_id: 'org-123',
|
|
298
|
+
event_days: 1,
|
|
299
|
+
event_typicalunit: 'km',
|
|
300
|
+
event_rounddown: false,
|
|
301
|
+
event_youthmultiplier: 1.0,
|
|
302
|
+
event_catering_email: 'test@example.com',
|
|
303
|
+
event_news: 'Test news',
|
|
304
|
+
event_billing: 'Test billing',
|
|
305
|
+
event_footer: 'Test footer',
|
|
306
|
+
event_email: 'event@example.com',
|
|
307
|
+
event_logo: null
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
311
|
+
data: [mockEventData],
|
|
312
|
+
error: null
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const { result } = renderHook(() => usePublicEvent('test-event', { enableCache: true }));
|
|
316
|
+
|
|
317
|
+
await waitFor(() => {
|
|
318
|
+
expect(result.current.isLoading).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const firstCallCount = mockSupabaseClient.rpc.mock.calls.length;
|
|
322
|
+
|
|
323
|
+
// Update mock to return different data
|
|
324
|
+
const updatedEventData = { ...mockEventData, event_name: 'Updated Event' };
|
|
325
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
326
|
+
data: [updatedEventData],
|
|
327
|
+
error: null
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await result.current.refetch();
|
|
331
|
+
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(result.current.event?.event_name).toBe('Updated Event');
|
|
334
|
+
}, { timeout: 2000 });
|
|
335
|
+
|
|
336
|
+
// Should have made another call
|
|
337
|
+
expect(mockSupabaseClient.rpc.mock.calls.length).toBeGreaterThan(firstCallCount);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('clears cache before refetching', async () => {
|
|
341
|
+
const mockEventData = {
|
|
342
|
+
event_id: '123',
|
|
343
|
+
event_name: 'Test Event',
|
|
344
|
+
event_date: '2024-01-01',
|
|
345
|
+
event_venue: 'Test Venue',
|
|
346
|
+
event_participants: 100,
|
|
347
|
+
event_colours: { primary: '#000000' },
|
|
348
|
+
organisation_id: 'org-123',
|
|
349
|
+
event_days: 1,
|
|
350
|
+
event_typicalunit: 'km',
|
|
351
|
+
event_rounddown: false,
|
|
352
|
+
event_youthmultiplier: 1.0,
|
|
353
|
+
event_catering_email: 'test@example.com',
|
|
354
|
+
event_news: 'Test news',
|
|
355
|
+
event_billing: 'Test billing',
|
|
356
|
+
event_footer: 'Test footer',
|
|
357
|
+
event_email: 'event@example.com',
|
|
358
|
+
event_logo: null
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
362
|
+
data: [mockEventData],
|
|
363
|
+
error: null
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const { result } = renderHook(() => usePublicEvent('test-event', { enableCache: true }));
|
|
367
|
+
|
|
368
|
+
await waitFor(() => {
|
|
369
|
+
expect(result.current.isLoading).toBe(false);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Verify cache exists
|
|
373
|
+
const statsBefore = getPublicEventCacheStats();
|
|
374
|
+
expect(statsBefore.size).toBeGreaterThan(0);
|
|
375
|
+
|
|
376
|
+
await result.current.refetch();
|
|
377
|
+
|
|
378
|
+
// Cache should be cleared and then repopulated
|
|
379
|
+
await waitFor(() => {
|
|
380
|
+
expect(result.current.event).toBeDefined();
|
|
381
|
+
}, { timeout: 2000 });
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe('Caching Options', () => {
|
|
386
|
+
it('respects cacheTtl option', async () => {
|
|
387
|
+
const mockEventData = {
|
|
388
|
+
event_id: '123',
|
|
389
|
+
event_name: 'Test Event',
|
|
390
|
+
event_date: '2024-01-01',
|
|
391
|
+
event_venue: 'Test Venue',
|
|
392
|
+
event_participants: 100,
|
|
393
|
+
event_colours: { primary: '#000000' },
|
|
394
|
+
organisation_id: 'org-123',
|
|
395
|
+
event_days: 1,
|
|
396
|
+
event_typicalunit: 'km',
|
|
397
|
+
event_rounddown: false,
|
|
398
|
+
event_youthmultiplier: 1.0,
|
|
399
|
+
event_catering_email: 'test@example.com',
|
|
400
|
+
event_news: 'Test news',
|
|
401
|
+
event_billing: 'Test billing',
|
|
402
|
+
event_footer: 'Test footer',
|
|
403
|
+
event_email: 'event@example.com',
|
|
404
|
+
event_logo: null
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
408
|
+
data: [mockEventData],
|
|
409
|
+
error: null
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const { result } = renderHook(() => usePublicEvent('test-event', {
|
|
413
|
+
enableCache: true,
|
|
414
|
+
cacheTtl: 1000 // 1 second
|
|
415
|
+
}));
|
|
416
|
+
|
|
417
|
+
await waitFor(() => {
|
|
418
|
+
expect(result.current.isLoading).toBe(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
expect(result.current.event).toBeTruthy();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('disables caching when enableCache is false', async () => {
|
|
425
|
+
const mockEventData = {
|
|
426
|
+
event_id: '123',
|
|
427
|
+
event_name: 'Test Event',
|
|
428
|
+
event_date: '2024-01-01',
|
|
429
|
+
event_venue: 'Test Venue',
|
|
430
|
+
event_participants: 100,
|
|
431
|
+
event_colours: { primary: '#000000' },
|
|
432
|
+
organisation_id: 'org-123',
|
|
433
|
+
event_days: 1,
|
|
434
|
+
event_typicalunit: 'km',
|
|
435
|
+
event_rounddown: false,
|
|
436
|
+
event_youthmultiplier: 1.0,
|
|
437
|
+
event_catering_email: 'test@example.com',
|
|
438
|
+
event_news: 'Test news',
|
|
439
|
+
event_billing: 'Test billing',
|
|
440
|
+
event_footer: 'Test footer',
|
|
441
|
+
event_email: 'event@example.com',
|
|
442
|
+
event_logo: null
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
446
|
+
data: [mockEventData],
|
|
447
|
+
error: null
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const { result, rerender } = renderHook(() => usePublicEvent('test-event', {
|
|
451
|
+
enableCache: false
|
|
452
|
+
}));
|
|
453
|
+
|
|
454
|
+
await waitFor(() => {
|
|
455
|
+
expect(result.current.isLoading).toBe(false);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const firstCallCount = mockSupabaseClient.rpc.mock.calls.length;
|
|
459
|
+
|
|
460
|
+
// Rerender - should make another call since caching is disabled
|
|
461
|
+
rerender();
|
|
462
|
+
|
|
463
|
+
await waitFor(() => {
|
|
464
|
+
expect(result.current.event).toBeTruthy();
|
|
465
|
+
}, { timeout: 2000 });
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('Error Handling', () => {
|
|
470
|
+
it('handles RPC errors that are not schema cache issues', async () => {
|
|
471
|
+
const rpcError = { message: 'Permission denied', code: 'PGRST301' };
|
|
472
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
473
|
+
data: null,
|
|
474
|
+
error: rpcError
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
478
|
+
|
|
479
|
+
await waitFor(() => {
|
|
480
|
+
expect(result.current.isLoading).toBe(false);
|
|
481
|
+
}, { timeout: 2000 });
|
|
482
|
+
|
|
483
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
484
|
+
expect(result.current.error?.message).toBe('Permission denied');
|
|
485
|
+
expect(result.current.event).toBe(null);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('handles exceptions during event fetch', async () => {
|
|
489
|
+
const error = new Error('Network timeout');
|
|
490
|
+
// Mock RPC to reject, and table access to also fail
|
|
491
|
+
mockSupabaseClient.rpc.mockRejectedValue(error);
|
|
492
|
+
mockSupabaseClient.from.mockReturnValue({
|
|
493
|
+
select: vi.fn().mockReturnThis(),
|
|
494
|
+
eq: vi.fn().mockReturnThis(),
|
|
495
|
+
not: vi.fn().mockReturnThis(),
|
|
496
|
+
limit: vi.fn().mockReturnThis(),
|
|
497
|
+
single: vi.fn().mockRejectedValue(error)
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
501
|
+
|
|
502
|
+
await waitFor(() => {
|
|
503
|
+
expect(result.current.isLoading).toBe(false);
|
|
504
|
+
}, { timeout: 2000 });
|
|
505
|
+
|
|
506
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
507
|
+
expect(result.current.error?.message).toBe('Network timeout');
|
|
508
|
+
expect(result.current.event).toBe(null);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('handles non-Error exceptions', async () => {
|
|
512
|
+
const stringError = 'String error';
|
|
513
|
+
// Mock RPC to reject, and table access to also fail
|
|
514
|
+
mockSupabaseClient.rpc.mockRejectedValue(stringError);
|
|
515
|
+
mockSupabaseClient.from.mockReturnValue({
|
|
516
|
+
select: vi.fn().mockReturnThis(),
|
|
517
|
+
eq: vi.fn().mockReturnThis(),
|
|
518
|
+
not: vi.fn().mockReturnThis(),
|
|
519
|
+
limit: vi.fn().mockReturnThis(),
|
|
520
|
+
single: vi.fn().mockRejectedValue(stringError)
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
524
|
+
|
|
525
|
+
await waitFor(() => {
|
|
526
|
+
expect(result.current.isLoading).toBe(false);
|
|
527
|
+
}, { timeout: 2000 });
|
|
528
|
+
|
|
529
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
530
|
+
expect(result.current.error?.message).toBe('Unknown error occurred');
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
describe('Parameter Changes', () => {
|
|
535
|
+
it('refetches when eventCode changes', async () => {
|
|
536
|
+
const mockEventData1 = {
|
|
537
|
+
event_id: '123',
|
|
538
|
+
event_name: 'Event 1',
|
|
539
|
+
event_date: '2024-01-01',
|
|
540
|
+
event_venue: 'Venue 1',
|
|
541
|
+
event_participants: 100,
|
|
542
|
+
event_colours: { primary: '#000000' },
|
|
543
|
+
organisation_id: 'org-123',
|
|
544
|
+
event_days: 1,
|
|
545
|
+
event_typicalunit: 'km',
|
|
546
|
+
event_rounddown: false,
|
|
547
|
+
event_youthmultiplier: 1.0,
|
|
548
|
+
event_catering_email: 'test@example.com',
|
|
549
|
+
event_news: 'Test news',
|
|
550
|
+
event_billing: 'Test billing',
|
|
551
|
+
event_footer: 'Test footer',
|
|
552
|
+
event_email: 'event@example.com',
|
|
553
|
+
event_logo: null
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const mockEventData2 = {
|
|
557
|
+
...mockEventData1,
|
|
558
|
+
event_id: '456',
|
|
559
|
+
event_name: 'Event 2'
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
mockSupabaseClient.rpc
|
|
563
|
+
.mockResolvedValueOnce({
|
|
564
|
+
data: [mockEventData1],
|
|
565
|
+
error: null
|
|
566
|
+
})
|
|
567
|
+
.mockResolvedValueOnce({
|
|
568
|
+
data: [mockEventData2],
|
|
569
|
+
error: null
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const { result, rerender } = renderHook(
|
|
573
|
+
({ eventCode }) => usePublicEvent(eventCode),
|
|
574
|
+
{ initialProps: { eventCode: 'event-1' } }
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
await waitFor(() => {
|
|
578
|
+
expect(result.current.isLoading).toBe(false);
|
|
579
|
+
}, { timeout: 2000 });
|
|
580
|
+
|
|
581
|
+
expect(result.current.event?.event_name).toBe('Event 1');
|
|
582
|
+
|
|
583
|
+
rerender({ eventCode: 'event-2' });
|
|
584
|
+
|
|
585
|
+
await waitFor(() => {
|
|
586
|
+
expect(result.current.isLoading).toBe(false);
|
|
587
|
+
}, { timeout: 2000 });
|
|
588
|
+
|
|
589
|
+
expect(result.current.event?.event_name).toBe('Event 2');
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe('Edge Cases', () => {
|
|
594
|
+
it('should handle null event data from RPC', async () => {
|
|
595
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
596
|
+
data: null,
|
|
597
|
+
error: null
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
601
|
+
|
|
602
|
+
await waitFor(() => {
|
|
603
|
+
expect(result.current.isLoading).toBe(false);
|
|
604
|
+
}, { timeout: 2000 });
|
|
605
|
+
|
|
606
|
+
expect(result.current.event).toBe(null);
|
|
607
|
+
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('should handle empty event data array from RPC', async () => {
|
|
611
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
612
|
+
data: [],
|
|
613
|
+
error: null
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
617
|
+
|
|
618
|
+
await waitFor(() => {
|
|
619
|
+
expect(result.current.isLoading).toBe(false);
|
|
620
|
+
}, { timeout: 2000 });
|
|
621
|
+
|
|
622
|
+
expect(result.current.event).toBe(null);
|
|
623
|
+
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('should handle undefined event data from RPC', async () => {
|
|
627
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
628
|
+
data: [undefined],
|
|
629
|
+
error: null
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
633
|
+
|
|
634
|
+
await waitFor(() => {
|
|
635
|
+
expect(result.current.isLoading).toBe(false);
|
|
636
|
+
}, { timeout: 2000 });
|
|
637
|
+
|
|
638
|
+
expect(result.current.event).toBe(null);
|
|
639
|
+
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('handles missing Supabase environment variables', async () => {
|
|
643
|
+
// Mock usePublicPageContext to return null environment
|
|
644
|
+
vi.mocked(usePublicPageContext).mockReturnValue({
|
|
645
|
+
environment: {
|
|
646
|
+
supabaseUrl: null,
|
|
647
|
+
supabaseKey: null
|
|
648
|
+
}
|
|
649
|
+
} as any);
|
|
650
|
+
|
|
651
|
+
// Mock environment variables to be undefined
|
|
652
|
+
Object.defineProperty(import.meta, 'env', {
|
|
653
|
+
value: {},
|
|
654
|
+
writable: true
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
658
|
+
|
|
659
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
660
|
+
|
|
661
|
+
await waitFor(() => {
|
|
662
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
663
|
+
expect.stringContaining('[WARN] [usePublicEvent] Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.')
|
|
664
|
+
);
|
|
665
|
+
}, { timeout: 2000 });
|
|
666
|
+
|
|
667
|
+
// Should still initialize but with error
|
|
668
|
+
expect(result.current.isLoading).toBe(false);
|
|
669
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
670
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
671
|
+
|
|
672
|
+
consoleWarnSpy.mockRestore();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('handles server-side rendering (window undefined)', () => {
|
|
676
|
+
// Test that the hook handles missing window by checking the implementation
|
|
677
|
+
// The hook checks `typeof window === 'undefined'` and returns null supabase client
|
|
678
|
+
// We can't actually delete window in the test environment as React needs it
|
|
679
|
+
// Instead, we test the behavior when supabase client is null (which happens when window is undefined)
|
|
680
|
+
|
|
681
|
+
// Mock usePublicPageContext to return null environment
|
|
682
|
+
vi.mocked(usePublicPageContext).mockReturnValue({
|
|
683
|
+
environment: {
|
|
684
|
+
supabaseUrl: null,
|
|
685
|
+
supabaseKey: null
|
|
686
|
+
}
|
|
687
|
+
} as any);
|
|
688
|
+
|
|
689
|
+
// Mock environment variables to be undefined
|
|
690
|
+
Object.defineProperty(import.meta, 'env', {
|
|
691
|
+
value: {},
|
|
692
|
+
writable: true
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
696
|
+
|
|
697
|
+
// When supabase client is null (as it would be in SSR), the hook should handle it gracefully
|
|
698
|
+
expect(result.current.isLoading).toBe(false);
|
|
699
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
700
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
});
|