@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.
Files changed (249) hide show
  1. package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
  2. package/dist/{DataTable-ON3IXISJ.js → DataTable-WKRZD47S.js} +6 -6
  3. package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +1 -1
  4. package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-FTSG5XH7.js} +3 -3
  5. package/dist/{api-I6UCQ5S6.js → api-IHKALJZD.js} +2 -2
  6. package/dist/{chunk-J2XXC7R5.js → chunk-6LTQQAT6.js} +77 -111
  7. package/dist/chunk-6LTQQAT6.js.map +1 -0
  8. package/dist/{chunk-STYK4OH2.js → chunk-6TQDD426.js} +10 -10
  9. package/dist/chunk-6TQDD426.js.map +1 -0
  10. package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
  11. package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
  12. package/dist/{chunk-73HSNNOQ.js → chunk-LOMZXPSN.js} +13 -13
  13. package/dist/{chunk-Y4BUBBHD.js → chunk-OETXORNB.js} +3 -3
  14. package/dist/{chunk-RUYZKXOD.js → chunk-ROXMHMY2.js} +5 -3
  15. package/dist/chunk-ROXMHMY2.js.map +1 -0
  16. package/dist/{chunk-SDMHPX3X.js → chunk-ULHIJK66.js} +56 -21
  17. package/dist/{chunk-SDMHPX3X.js.map → chunk-ULHIJK66.js.map} +1 -1
  18. package/dist/{chunk-VVBAW5A5.js → chunk-VKB2CO4Z.js} +46 -35
  19. package/dist/chunk-VKB2CO4Z.js.map +1 -0
  20. package/dist/{chunk-HQVPB5MZ.js → chunk-VRGWKHDB.js} +6 -6
  21. package/dist/{chunk-NIU6J6OX.js → chunk-XNYQOL3Z.js} +16 -16
  22. package/dist/chunk-XNYQOL3Z.js.map +1 -0
  23. package/dist/{chunk-4QYC5L4K.js → chunk-XYXSXPUK.js} +22 -27
  24. package/dist/chunk-XYXSXPUK.js.map +1 -0
  25. package/dist/components.d.ts +3 -3
  26. package/dist/components.js +8 -8
  27. package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
  28. package/dist/hooks.d.ts +12 -12
  29. package/dist/hooks.js +7 -7
  30. package/dist/index.d.ts +7 -7
  31. package/dist/index.js +18 -23
  32. package/dist/index.js.map +1 -1
  33. package/dist/providers.js +2 -2
  34. package/dist/rbac/index.d.ts +1 -1
  35. package/dist/rbac/index.js +6 -6
  36. package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
  37. package/dist/types.d.ts +2 -2
  38. package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
  39. package/dist/utils.d.ts +8 -8
  40. package/dist/utils.js +2 -2
  41. package/docs/api/classes/ColumnFactory.md +1 -1
  42. package/docs/api/classes/ErrorBoundary.md +1 -1
  43. package/docs/api/classes/InvalidScopeError.md +1 -1
  44. package/docs/api/classes/Logger.md +1 -1
  45. package/docs/api/classes/MissingUserContextError.md +1 -1
  46. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  47. package/docs/api/classes/PermissionDeniedError.md +1 -1
  48. package/docs/api/classes/RBACAuditManager.md +2 -2
  49. package/docs/api/classes/RBACCache.md +1 -1
  50. package/docs/api/classes/RBACEngine.md +2 -2
  51. package/docs/api/classes/RBACError.md +1 -1
  52. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  53. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  54. package/docs/api/classes/StorageUtils.md +1 -1
  55. package/docs/api/enums/FileCategory.md +1 -1
  56. package/docs/api/enums/LogLevel.md +1 -1
  57. package/docs/api/enums/RBACErrorCode.md +1 -1
  58. package/docs/api/enums/RPCFunction.md +1 -1
  59. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  60. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  63. package/docs/api/interfaces/AvatarProps.md +1 -1
  64. package/docs/api/interfaces/BadgeProps.md +1 -1
  65. package/docs/api/interfaces/ButtonProps.md +1 -1
  66. package/docs/api/interfaces/CalendarProps.md +1 -1
  67. package/docs/api/interfaces/CardProps.md +1 -1
  68. package/docs/api/interfaces/ColorPalette.md +1 -1
  69. package/docs/api/interfaces/ColorShade.md +1 -1
  70. package/docs/api/interfaces/ComplianceResult.md +1 -1
  71. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  72. package/docs/api/interfaces/DataRecord.md +1 -1
  73. package/docs/api/interfaces/DataTableAction.md +1 -1
  74. package/docs/api/interfaces/DataTableColumn.md +1 -1
  75. package/docs/api/interfaces/DataTableProps.md +1 -1
  76. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  77. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  78. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  79. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  80. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  81. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  82. package/docs/api/interfaces/ExportColumn.md +1 -1
  83. package/docs/api/interfaces/ExportOptions.md +1 -1
  84. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  85. package/docs/api/interfaces/FileMetadata.md +1 -1
  86. package/docs/api/interfaces/FileReference.md +1 -1
  87. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  88. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  89. package/docs/api/interfaces/FileUploadProps.md +1 -1
  90. package/docs/api/interfaces/FooterProps.md +1 -1
  91. package/docs/api/interfaces/FormFieldProps.md +1 -1
  92. package/docs/api/interfaces/FormProps.md +1 -1
  93. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  94. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  95. package/docs/api/interfaces/InputProps.md +1 -1
  96. package/docs/api/interfaces/LabelProps.md +1 -1
  97. package/docs/api/interfaces/LoggerConfig.md +1 -1
  98. package/docs/api/interfaces/LoginFormProps.md +1 -1
  99. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  100. package/docs/api/interfaces/NavigationContextType.md +1 -1
  101. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  102. package/docs/api/interfaces/NavigationItem.md +1 -1
  103. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  104. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  105. package/docs/api/interfaces/Organisation.md +1 -1
  106. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  107. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  108. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  109. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  110. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  111. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  112. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  113. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  114. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  115. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  116. package/docs/api/interfaces/PaletteData.md +1 -1
  117. package/docs/api/interfaces/ParsedAddress.md +2 -2
  118. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  119. package/docs/api/interfaces/ProgressProps.md +1 -1
  120. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  121. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  122. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  123. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  124. package/docs/api/interfaces/QuickFix.md +1 -1
  125. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  126. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  127. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  128. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  129. package/docs/api/interfaces/RBACConfig.md +2 -2
  130. package/docs/api/interfaces/RBACContext.md +1 -1
  131. package/docs/api/interfaces/RBACLogger.md +1 -1
  132. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  133. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  134. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  135. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  136. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  137. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  138. package/docs/api/interfaces/RBACResult.md +1 -1
  139. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  140. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  141. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  142. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  143. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  144. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  145. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  146. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  147. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  148. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  149. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  150. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  151. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  152. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  153. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  154. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  155. package/docs/api/interfaces/RouteConfig.md +1 -1
  156. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  157. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  158. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  159. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  160. package/docs/api/interfaces/SetupIssue.md +1 -1
  161. package/docs/api/interfaces/StorageConfig.md +1 -1
  162. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  163. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  164. package/docs/api/interfaces/StorageListOptions.md +1 -1
  165. package/docs/api/interfaces/StorageListResult.md +1 -1
  166. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  167. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  168. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  169. package/docs/api/interfaces/StyleImport.md +1 -1
  170. package/docs/api/interfaces/SwitchProps.md +1 -1
  171. package/docs/api/interfaces/TabsContentProps.md +1 -1
  172. package/docs/api/interfaces/TabsListProps.md +1 -1
  173. package/docs/api/interfaces/TabsProps.md +1 -1
  174. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  175. package/docs/api/interfaces/TextareaProps.md +1 -1
  176. package/docs/api/interfaces/ToastActionElement.md +1 -1
  177. package/docs/api/interfaces/ToastProps.md +1 -1
  178. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  179. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  180. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  181. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  182. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  183. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  184. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  185. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  186. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  187. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  188. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  189. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  190. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  191. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  192. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  193. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  194. package/docs/api/interfaces/UserEventAccess.md +1 -1
  195. package/docs/api/interfaces/UserMenuProps.md +1 -1
  196. package/docs/api/interfaces/UserProfile.md +1 -1
  197. package/docs/api/modules.md +16 -16
  198. package/docs/migration/README.md +18 -0
  199. package/docs/migration/database-changes-december-2025.md +767 -0
  200. package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
  201. package/package.json +1 -1
  202. package/src/__tests__/public-recipe-view.test.ts +10 -10
  203. package/src/__tests__/rls-policies.test.ts +13 -13
  204. package/src/components/AddressField/README.md +6 -6
  205. package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -15
  206. package/src/components/Select/Select.test.tsx +4 -1
  207. package/src/components/Select/Select.tsx +60 -15
  208. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
  209. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
  210. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
  211. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
  212. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +9 -8
  213. package/src/hooks/public/usePublicEvent.ts +8 -8
  214. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  215. package/src/hooks/useFileDisplay.ts +8 -9
  216. package/src/hooks/useQueryCache.ts +6 -6
  217. package/src/hooks/useSecureDataAccess.test.ts +8 -8
  218. package/src/hooks/useSecureDataAccess.ts +15 -11
  219. package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
  220. package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
  221. package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
  222. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
  223. package/src/rbac/utils/eventContext.ts +5 -2
  224. package/src/services/AuthService.ts +37 -8
  225. package/src/services/OrganisationService.ts +92 -139
  226. package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
  227. package/src/services/__tests__/OrganisationService.test.ts +218 -86
  228. package/src/types/database.generated.ts +166 -201
  229. package/src/types/supabase.ts +2 -2
  230. package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
  231. package/src/utils/file-reference/index.ts +4 -4
  232. package/src/utils/google-places/googlePlacesUtils.ts +1 -1
  233. package/src/utils/google-places/types.ts +1 -1
  234. package/src/utils/request-deduplication.ts +4 -4
  235. package/src/utils/security/secureDataAccess.test.ts +1 -1
  236. package/src/utils/security/secureDataAccess.ts +7 -4
  237. package/src/utils/storage/README.md +1 -1
  238. package/dist/chunk-4QYC5L4K.js.map +0 -1
  239. package/dist/chunk-J2XXC7R5.js.map +0 -1
  240. package/dist/chunk-NIU6J6OX.js.map +0 -1
  241. package/dist/chunk-RUYZKXOD.js.map +0 -1
  242. package/dist/chunk-STYK4OH2.js.map +0 -1
  243. package/dist/chunk-VVBAW5A5.js.map +0 -1
  244. /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-WKRZD47S.js.map} +0 -0
  245. /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
  246. /package/dist/{api-I6UCQ5S6.js.map → api-IHKALJZD.js.map} +0 -0
  247. /package/dist/{chunk-73HSNNOQ.js.map → chunk-LOMZXPSN.js.map} +0 -0
  248. /package/dist/{chunk-Y4BUBBHD.js.map → chunk-OETXORNB.js.map} +0 -0
  249. /package/dist/{chunk-HQVPB5MZ.js.map → chunk-VRGWKHDB.js.map} +0 -0
@@ -0,0 +1,581 @@
1
+ import React from 'react';
2
+ import { renderHook, waitFor, act } 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
+ // Helper to create table query mocks
36
+ const createTableQueryMock = () => ({
37
+ data: null,
38
+ error: null,
39
+ mockResolvedValueOnce: vi.fn(),
40
+ mockResolvedValue: vi.fn()
41
+ });
42
+
43
+ // Helper to create a complete table query chain
44
+ const createTableQueryChain = (finalResult: any) => {
45
+ const mockChain = {
46
+ select: vi.fn().mockReturnThis(),
47
+ eq: vi.fn().mockReturnThis(),
48
+ not: vi.fn().mockReturnThis(),
49
+ limit: vi.fn().mockReturnThis(),
50
+ single: vi.fn().mockResolvedValue(finalResult)
51
+ };
52
+ return mockChain;
53
+ };
54
+
55
+ // Mock createClient
56
+ vi.mock('@supabase/supabase-js', () => ({
57
+ createClient: vi.fn(() => mockSupabaseClient)
58
+ }));
59
+
60
+ // Mock environment variables
61
+ const originalEnv = import.meta.env;
62
+
63
+ describe('usePublicEvent', () => {
64
+ beforeEach(() => {
65
+ vi.clearAllMocks();
66
+ clearPublicEventCache();
67
+
68
+ // Reset environment
69
+ Object.defineProperty(import.meta, 'env', {
70
+ value: {
71
+ VITE_SUPABASE_URL: 'https://test.supabase.co',
72
+ VITE_SUPABASE_ANON_KEY: 'test-anon-key'
73
+ },
74
+ writable: true
75
+ });
76
+
77
+ // Mock window object
78
+ Object.defineProperty(window, 'location', {
79
+ value: { href: 'https://test.com' },
80
+ writable: true
81
+ });
82
+
83
+ // Re-establish mock implementations after clearAllMocks
84
+ mockSupabaseClient.rpc.mockImplementation(() =>
85
+ Promise.resolve({
86
+ data: null,
87
+ error: null
88
+ })
89
+ );
90
+
91
+ // Ensure the mock is properly configured
92
+ expect(mockSupabaseClient.rpc).toBeDefined();
93
+ });
94
+
95
+ afterEach(() => {
96
+ vi.clearAllMocks();
97
+ clearPublicEventCache();
98
+ Object.defineProperty(import.meta, 'env', {
99
+ value: originalEnv,
100
+ writable: true
101
+ });
102
+ });
103
+
104
+ describe('Basic Functionality', () => {
105
+ it('should initialize with loading state', () => {
106
+ const { result } = renderHook(() => usePublicEvent('test-event'));
107
+
108
+ expect(result.current.isLoading).toBe(true);
109
+ expect(result.current.event).toBe(null);
110
+ expect(result.current.error).toBe(null);
111
+ });
112
+
113
+ it('should fetch event data successfully via RPC', async () => {
114
+ const mockEventData = {
115
+ event_id: '123',
116
+ event_name: 'Test Event',
117
+ event_date: '2024-01-01',
118
+ event_venue: 'Test Venue',
119
+ event_participants: 100,
120
+ event_colours: { primary: '#000000' },
121
+ organisation_id: 'org-123',
122
+ event_days: 1,
123
+ event_typicalunit: 'km',
124
+ event_rounddown: false,
125
+ event_youthmultiplier: 1.0,
126
+ event_catering_email: 'test@example.com',
127
+ event_news: 'Test news',
128
+ event_billing: 'Test billing',
129
+ event_footer: 'Test footer',
130
+ event_email: 'event@example.com',
131
+ event_logo: null
132
+ };
133
+
134
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
135
+ data: [mockEventData],
136
+ error: null
137
+ });
138
+
139
+ const { result } = renderHook(() => usePublicEvent('test-event'));
140
+
141
+ await waitFor(() => {
142
+ expect(result.current.isLoading).toBe(false);
143
+ }, { interval: 10 });
144
+
145
+ expect(result.current.event).toEqual({
146
+ id: '123',
147
+ event_id: '123',
148
+ event_name: 'Test Event',
149
+ event_code: 'test-event',
150
+ event_date: '2024-01-01',
151
+ event_venue: 'Test Venue',
152
+ event_participants: 100,
153
+ event_logo: null,
154
+ event_colours: { primary: '#000000' },
155
+ organisation_id: 'org-123',
156
+ is_visible: true,
157
+ created_at: expect.any(String),
158
+ updated_at: expect.any(String)
159
+ });
160
+ expect(result.current.error).toBe(null);
161
+ });
162
+
163
+ it('should handle event not found', async () => {
164
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
165
+ data: [],
166
+ error: null
167
+ });
168
+
169
+ const { result } = renderHook(() => usePublicEvent('nonexistent-event'));
170
+
171
+ await waitFor(() => {
172
+ expect(result.current.isLoading).toBe(false);
173
+ }, { interval: 10 });
174
+
175
+ expect(result.current.event).toBe(null);
176
+ expect(result.current.error).toEqual(new Error('Event not found'));
177
+ });
178
+
179
+ it('should handle invalid event code', async () => {
180
+ const { result } = renderHook(() => usePublicEvent(''));
181
+
182
+ await waitFor(() => {
183
+ expect(result.current.isLoading).toBe(false);
184
+ }, { interval: 10 });
185
+
186
+ expect(result.current.event).toBe(null);
187
+ expect(result.current.error).toBeInstanceOf(Error);
188
+ expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
189
+ });
190
+ });
191
+
192
+ describe('Caching', () => {
193
+ it('should cache event data', async () => {
194
+ const mockEventData = {
195
+ event_id: '123',
196
+ event_name: 'Test Event',
197
+ event_date: '2024-01-01',
198
+ event_venue: 'Test Venue',
199
+ event_participants: 100,
200
+ event_colours: { primary: '#000000' },
201
+ organisation_id: 'org-123',
202
+ event_days: 1,
203
+ event_typicalunit: 'km',
204
+ event_rounddown: false,
205
+ event_youthmultiplier: 1.0,
206
+ event_catering_email: 'test@example.com',
207
+ event_news: 'Test news',
208
+ event_billing: 'Test billing',
209
+ event_footer: 'Test footer',
210
+ event_email: 'event@example.com'
211
+ };
212
+
213
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
214
+ data: [mockEventData],
215
+ error: null
216
+ });
217
+
218
+ const { result, rerender } = renderHook(() => usePublicEvent('test-event'));
219
+
220
+ await waitFor(() => {
221
+ expect(result.current.isLoading).toBe(false);
222
+ }, { interval: 10 });
223
+
224
+ // Rerender with same event code - should use cache
225
+ rerender();
226
+
227
+ // Should not call RPC again
228
+ expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(1);
229
+ });
230
+
231
+ it('should respect cache TTL', async () => {
232
+ const mockEventData = {
233
+ event_id: '123',
234
+ event_name: 'Test Event',
235
+ event_date: '2024-01-01',
236
+ event_venue: 'Test Venue',
237
+ event_participants: 100,
238
+ event_colours: { primary: '#000000' },
239
+ organisation_id: 'org-123',
240
+ event_days: 1,
241
+ event_typicalunit: 'km',
242
+ event_rounddown: false,
243
+ event_youthmultiplier: 1.0,
244
+ event_catering_email: 'test@example.com',
245
+ event_news: 'Test news',
246
+ event_billing: 'Test billing',
247
+ event_footer: 'Test footer',
248
+ event_email: 'event@example.com'
249
+ };
250
+
251
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
252
+ data: [mockEventData],
253
+ error: null
254
+ });
255
+
256
+ const { result } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
257
+
258
+ await waitFor(() => {
259
+ expect(result.current.isLoading).toBe(false);
260
+ }, { interval: 10 });
261
+
262
+ // Wait for cache to expire
263
+ await new Promise(resolve => setTimeout(resolve, 150));
264
+
265
+ // Rerender should fetch again
266
+ const { result: result2 } = renderHook(() => usePublicEvent('test-event', { cacheTtl: 100 }));
267
+
268
+ await waitFor(() => {
269
+ expect(result2.current.isLoading).toBe(false);
270
+ }, { interval: 10 });
271
+
272
+ expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
273
+ });
274
+
275
+ it('should disable caching when requested', async () => {
276
+ const mockEventData = {
277
+ event_id: '123',
278
+ event_name: 'Test Event',
279
+ event_date: '2024-01-01',
280
+ event_venue: 'Test Venue',
281
+ event_participants: 100,
282
+ event_colours: { primary: '#000000' },
283
+ organisation_id: 'org-123',
284
+ event_days: 1,
285
+ event_typicalunit: 'km',
286
+ event_rounddown: false,
287
+ event_youthmultiplier: 1.0,
288
+ event_catering_email: 'test@example.com',
289
+ event_news: 'Test news',
290
+ event_billing: 'Test billing',
291
+ event_footer: 'Test footer',
292
+ event_email: 'event@example.com',
293
+ event_logo: null
294
+ };
295
+
296
+ // Clear any existing cache
297
+ const { clearPublicEventCache } = await import('../public/usePublicEvent');
298
+ clearPublicEventCache();
299
+
300
+ // Use mockImplementation to handle multiple calls
301
+ let callCount = 0;
302
+ mockSupabaseClient.rpc.mockImplementation(() => {
303
+ callCount++;
304
+ return Promise.resolve({
305
+ data: [mockEventData],
306
+ error: null
307
+ });
308
+ });
309
+
310
+ const { result, rerender } = renderHook(
311
+ ({ eventCode }) => usePublicEvent(eventCode, { enableCache: false }),
312
+ { initialProps: { eventCode: 'test-event' } }
313
+ );
314
+
315
+ await waitFor(() => {
316
+ expect(result.current.isLoading).toBe(false);
317
+ }, { interval: 10 });
318
+
319
+ // Change the event code to force a new fetch
320
+ rerender({ eventCode: 'test-event-2' });
321
+
322
+ await waitFor(() => {
323
+ expect(result.current.isLoading).toBe(false);
324
+ }, { interval: 10 });
325
+
326
+ expect(callCount).toBe(2);
327
+ });
328
+ });
329
+
330
+ describe('Error Handling', () => {
331
+ it('should handle RPC errors', async () => {
332
+ // Mock the RPC call to return an error object
333
+ const testError = { message: 'Database error', details: 'Test error details', hint: null, code: 'TEST_ERROR' };
334
+ mockSupabaseClient.rpc.mockImplementation(() =>
335
+ Promise.resolve({
336
+ data: null,
337
+ error: testError
338
+ })
339
+ );
340
+
341
+ const { result } = renderHook(() => usePublicEvent('test-event'));
342
+
343
+ await waitFor(() => {
344
+ expect(result.current.isLoading).toBe(false);
345
+ }, { interval: 10 });
346
+
347
+ expect(result.current.error).toBeInstanceOf(Error);
348
+ expect(result.current.error?.message).toBe('Database error');
349
+ expect(result.current.event).toBe(null);
350
+
351
+ // Verify the mock was called
352
+ expect(mockSupabaseClient.rpc).toHaveBeenCalledWith('get_public_event_by_code', {
353
+ event_code_param: 'test-event'
354
+ });
355
+ });
356
+
357
+ it('should handle missing Supabase client', async () => {
358
+ // Mock the PublicPageProvider to return null environment
359
+ const mockUsePublicPageContext = vi.mocked(usePublicPageContext);
360
+ mockUsePublicPageContext.mockReturnValueOnce({
361
+ environment: {
362
+ supabaseUrl: '',
363
+ supabaseKey: ''
364
+ }
365
+ });
366
+
367
+ // Mock createClient to return null when environment is empty
368
+ const { createClient } = await import('@supabase/supabase-js');
369
+ vi.mocked(createClient).mockReturnValueOnce(null as any);
370
+
371
+ const { result } = renderHook(() => usePublicEvent('test-event'));
372
+
373
+ await waitFor(() => {
374
+ expect(result.current.isLoading).toBe(false);
375
+ }, { interval: 10 });
376
+
377
+ expect(result.current.error).toBeInstanceOf(Error);
378
+ expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
379
+ expect(result.current.event).toBe(null);
380
+ });
381
+ });
382
+
383
+ describe('Provider Context Integration', () => {
384
+ it('should use PublicPageContext when available', async () => {
385
+ const mockContext = {
386
+ environment: {
387
+ supabaseUrl: 'https://context.supabase.co',
388
+ supabaseKey: 'context-anon-key'
389
+ }
390
+ };
391
+
392
+ vi.mocked(usePublicPageContext).mockReturnValue(mockContext);
393
+
394
+ const mockEventData = {
395
+ event_id: '123',
396
+ event_name: 'Test Event',
397
+ event_date: '2024-01-01',
398
+ event_venue: 'Test Venue',
399
+ event_participants: 100,
400
+ event_colours: { primary: '#000000' },
401
+ organisation_id: 'org-123',
402
+ event_days: 1,
403
+ event_typicalunit: 'km',
404
+ event_rounddown: false,
405
+ event_youthmultiplier: 1.0,
406
+ event_catering_email: 'test@example.com',
407
+ event_news: 'Test news',
408
+ event_billing: 'Test billing',
409
+ event_footer: 'Test footer',
410
+ event_email: 'event@example.com',
411
+ event_logo: null
412
+ };
413
+
414
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
415
+ data: [mockEventData],
416
+ error: null
417
+ });
418
+
419
+ const { result } = renderHook(() => usePublicEvent('test-event'));
420
+
421
+ await waitFor(() => {
422
+ expect(result.current.isLoading).toBe(false);
423
+ }, { interval: 10 });
424
+
425
+ expect(result.current.event).toBeTruthy();
426
+ });
427
+ });
428
+
429
+ describe('Refetch Functionality', () => {
430
+ it('should refetch data when refetch is called', async () => {
431
+ const mockEventData = {
432
+ event_id: '123',
433
+ event_name: 'Test Event',
434
+ event_date: '2024-01-01',
435
+ event_venue: 'Test Venue',
436
+ event_participants: 100,
437
+ event_colours: { primary: '#000000' },
438
+ organisation_id: 'org-123',
439
+ event_days: 1,
440
+ event_typicalunit: 'km',
441
+ event_rounddown: false,
442
+ event_youthmultiplier: 1.0,
443
+ event_catering_email: 'test@example.com',
444
+ event_news: 'Test news',
445
+ event_billing: 'Test billing',
446
+ event_footer: 'Test footer',
447
+ event_email: 'event@example.com'
448
+ };
449
+
450
+ mockSupabaseClient.rpc.mockResolvedValue({
451
+ data: [mockEventData],
452
+ error: null
453
+ });
454
+
455
+ const { result } = renderHook(() => usePublicEvent('test-event'));
456
+
457
+ await waitFor(() => {
458
+ expect(result.current.isLoading).toBe(false);
459
+ }, { interval: 10 });
460
+
461
+ // Call refetch
462
+ await act(async () => {
463
+ await result.current.refetch();
464
+ });
465
+
466
+ expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
467
+ });
468
+
469
+ it('should clear cache when refetch is called', async () => {
470
+ const mockEventData = {
471
+ event_id: '123',
472
+ event_name: 'Test Event',
473
+ event_date: '2024-01-01',
474
+ event_venue: 'Test Venue',
475
+ event_participants: 100,
476
+ event_colours: { primary: '#000000' },
477
+ organisation_id: 'org-123',
478
+ event_days: 1,
479
+ event_typicalunit: 'km',
480
+ event_rounddown: false,
481
+ event_youthmultiplier: 1.0,
482
+ event_catering_email: 'test@example.com',
483
+ event_news: 'Test news',
484
+ event_billing: 'Test billing',
485
+ event_footer: 'Test footer',
486
+ event_email: 'event@example.com'
487
+ };
488
+
489
+ mockSupabaseClient.rpc.mockResolvedValue({
490
+ data: [mockEventData],
491
+ error: null
492
+ });
493
+
494
+ const { result } = renderHook(() => usePublicEvent('test-event'));
495
+
496
+ await waitFor(() => {
497
+ expect(result.current.isLoading).toBe(false);
498
+ }, { interval: 10 });
499
+
500
+ // Call refetch
501
+ await act(async () => {
502
+ await result.current.refetch();
503
+ });
504
+
505
+ // Should call RPC again (cache was cleared)
506
+ expect(mockSupabaseClient.rpc).toHaveBeenCalledTimes(2);
507
+ });
508
+ });
509
+
510
+ describe('Cache Management Utilities', () => {
511
+ it('should clear public event cache', () => {
512
+ // Add some data to cache
513
+ const stats = getPublicEventCacheStats();
514
+ expect(stats.size).toBe(0);
515
+
516
+ // Clear cache
517
+ clearPublicEventCache();
518
+
519
+ const statsAfter = getPublicEventCacheStats();
520
+ expect(statsAfter.size).toBe(0);
521
+ });
522
+
523
+ it('should get cache statistics', () => {
524
+ const stats = getPublicEventCacheStats();
525
+ expect(stats).toEqual({
526
+ size: 0,
527
+ keys: []
528
+ });
529
+ });
530
+ });
531
+
532
+ describe('Edge Cases', () => {
533
+ it('should handle null event data from RPC', async () => {
534
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
535
+ data: null,
536
+ error: null
537
+ });
538
+
539
+ const { result } = renderHook(() => usePublicEvent('test-event'));
540
+
541
+ await waitFor(() => {
542
+ expect(result.current.isLoading).toBe(false);
543
+ }, { interval: 10 });
544
+
545
+ expect(result.current.event).toBe(null);
546
+ expect(result.current.error).toEqual(new Error('Event not found'));
547
+ });
548
+
549
+ it('should handle empty event data array from RPC', async () => {
550
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
551
+ data: [],
552
+ error: null
553
+ });
554
+
555
+ const { result } = renderHook(() => usePublicEvent('test-event'));
556
+
557
+ await waitFor(() => {
558
+ expect(result.current.isLoading).toBe(false);
559
+ }, { interval: 10 });
560
+
561
+ expect(result.current.event).toBe(null);
562
+ expect(result.current.error).toEqual(new Error('Event not found'));
563
+ });
564
+
565
+ it('should handle undefined event data from RPC', async () => {
566
+ mockSupabaseClient.rpc.mockResolvedValueOnce({
567
+ data: [undefined],
568
+ error: null
569
+ });
570
+
571
+ const { result } = renderHook(() => usePublicEvent('test-event'));
572
+
573
+ await waitFor(() => {
574
+ expect(result.current.isLoading).toBe(false);
575
+ }, { interval: 10 });
576
+
577
+ expect(result.current.event).toBe(null);
578
+ expect(result.current.error).toEqual(new Error('Event not found'));
579
+ });
580
+ });
581
+ });
@@ -400,7 +400,7 @@ describe('useSecureDataAccess', () => {
400
400
 
401
401
  const { result } = renderHook(() => useSecureDataAccess());
402
402
 
403
- const data = await result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 });
403
+ const data = await result.current.secureUpdate('core_events', { name: 'Updated' }, { id: 1 });
404
404
 
405
405
  expect(data).toEqual([{ id: 1, name: 'Updated', organisation_id: 'org-123' }]);
406
406
  });
@@ -421,7 +421,7 @@ describe('useSecureDataAccess', () => {
421
421
 
422
422
  const { result } = renderHook(() => useSecureDataAccess());
423
423
 
424
- await expect(result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 })).rejects.toThrow('Update failed');
424
+ await expect(result.current.secureUpdate('core_events', { name: 'Updated' }, { id: 1 })).rejects.toThrow('Update failed');
425
425
  });
426
426
 
427
427
  it('should return empty array when no data updated', async () => {
@@ -440,7 +440,7 @@ describe('useSecureDataAccess', () => {
440
440
 
441
441
  const { result } = renderHook(() => useSecureDataAccess());
442
442
 
443
- const data = await result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 });
443
+ const data = await result.current.secureUpdate('core_events', { name: 'Updated' }, { id: 1 });
444
444
 
445
445
  expect(data).toEqual([]);
446
446
  });
@@ -461,9 +461,9 @@ describe('useSecureDataAccess', () => {
461
461
 
462
462
  const { result } = renderHook(() => useSecureDataAccess());
463
463
 
464
- await result.current.secureDelete('event', { id: 1 });
464
+ await result.current.secureDelete('core_events', { id: 1 });
465
465
 
466
- expect(mockSupabase.from).toHaveBeenCalledWith('event');
466
+ expect(mockSupabase.from).toHaveBeenCalledWith('core_events');
467
467
  });
468
468
 
469
469
  it('should handle delete errors', async () => {
@@ -480,7 +480,8 @@ describe('useSecureDataAccess', () => {
480
480
 
481
481
  const { result } = renderHook(() => useSecureDataAccess());
482
482
 
483
- await expect(result.current.secureDelete('event', { id: 1 })).rejects.toThrow('Delete failed');
483
+ // The implementation throws the error, so the promise should reject
484
+ await expect(result.current.secureDelete('core_events', { id: 1 })).rejects.toThrow('Delete failed');
484
485
  });
485
486
  });
486
487
 
@@ -605,12 +606,12 @@ describe('useSecureDataAccess', () => {
605
606
  const { result } = renderHook(() => useSecureDataAccess());
606
607
 
607
608
  // Verify the actual behavior: secure update works correctly
608
- const updateResult = await result.current.secureUpdate('event', { name: 'Updated' }, { id: 1 });
609
+ const updateResult = await result.current.secureUpdate('core_events', { name: 'Updated' }, { id: 1 });
609
610
 
610
611
  // Test the actual functionality: update executes and returns results
611
612
  expect(updateResult).toEqual([{ id: 1, name: 'Updated' }]);
612
613
  // Verify security: organisation filter is applied
613
- expect(mockSupabase.from).toHaveBeenCalledWith('event');
614
+ expect(mockSupabase.from).toHaveBeenCalledWith('core_events');
614
615
  // Verify the operation completed successfully (organisation filter is applied internally)
615
616
 
616
617
  consoleSpy.mockRestore();
@@ -210,7 +210,7 @@ export function usePublicEvent(
210
210
 
211
211
  // Fallback: Direct table access with public RLS policy
212
212
  const tableResponse2 = await (supabase as any)
213
- .from('event')
213
+ .from('core_events')
214
214
  .select(`
215
215
  event_id,
216
216
  event_name,
@@ -247,11 +247,11 @@ export function usePublicEvent(
247
247
  return;
248
248
  }
249
249
 
250
- // Get event logo from file_references
250
+ // Get event logo from core_file_references
251
251
  const logoResponse = await (supabase as any)
252
- .from('file_references')
252
+ .from('core_file_references')
253
253
  .select('file_path')
254
- .eq('table_name', 'event')
254
+ .eq('table_name', 'core_events')
255
255
  .eq('record_id', tableData.event_id)
256
256
  .eq('is_public', true)
257
257
  .eq('file_metadata->>category', 'event_logos')
@@ -286,7 +286,7 @@ export function usePublicEvent(
286
286
  logger.warn('usePublicEvent', 'RPC call failed, falling back to direct table access:', rpcError);
287
287
 
288
288
  const tableResponse = await (supabase as any)
289
- .from('event')
289
+ .from('core_events')
290
290
  .select(`
291
291
  event_id,
292
292
  event_name,
@@ -323,11 +323,11 @@ export function usePublicEvent(
323
323
  return;
324
324
  }
325
325
 
326
- // Get event logo from file_references
326
+ // Get event logo from core_file_references
327
327
  const logoResponse = await (supabase as any)
328
- .from('file_references')
328
+ .from('core_file_references')
329
329
  .select('file_path')
330
- .eq('table_name', 'event')
330
+ .eq('table_name', 'core_events')
331
331
  .eq('record_id', tableData.event_id)
332
332
  .eq('is_public', true)
333
333
  .eq('file_metadata->>category', 'event_logos')