@jmruthers/pace-core 0.5.114 → 0.5.116
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/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
- package/dist/{DataTable-3JRLZXER.js → DataTable-ZOAKQ3SU.js} +10 -9
- package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
- package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
- package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
- package/dist/{chunk-4OX5PXHX.js → chunk-2GJ5GL77.js} +4 -5
- package/dist/chunk-2GJ5GL77.js.map +1 -0
- package/dist/{chunk-5YIZFEUQ.js → chunk-2LM4QQGH.js} +31 -35
- package/dist/chunk-2LM4QQGH.js.map +1 -0
- package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
- package/dist/chunk-3DBFLLLU.js.map +1 -0
- package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
- package/dist/chunk-ECOVPXYS.js.map +1 -0
- package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
- package/dist/chunk-KA3PSVNV.js.map +1 -0
- package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
- package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
- package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
- package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
- package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
- package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
- package/dist/chunk-P3PUOL6B.js.map +1 -0
- package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
- package/dist/chunk-PHDAXDHB.js.map +1 -0
- package/dist/chunk-UJI6WSMD.js +201 -0
- package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
- package/dist/{chunk-JHWQNJP3.js → chunk-UKZWNQMB.js} +65 -19
- package/dist/{chunk-JHWQNJP3.js.map → chunk-UKZWNQMB.js.map} +1 -1
- package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
- package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +10 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +3 -2
- package/dist/rbac/index.d.ts +82 -1
- package/dist/rbac/index.js +13 -10
- package/dist/{useToast-DRah6K-g.d.ts → useToast-Cs_g32bg.d.ts} +8 -6
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/dist/validation.js +3 -1
- package/dist/validation.js.map +1 -1
- package/docs/README.md +4 -0
- 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 +35 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +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/EventAppRoleData.md +71 -0
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +52 -0
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +43 -16
- package/docs/architecture/rpc-function-standards.md +193 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +244 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
- package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
- package/src/components/DataTable/components/DataTableCore.tsx +32 -17
- package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
- package/src/components/DataTable/components/EditableRow.tsx +18 -1
- package/src/components/DataTable/components/ImportModal.tsx +25 -2
- package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
- package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
- package/src/components/EventSelector/EventSelector.tsx +5 -25
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
- package/src/components/Select/Select.tsx +8 -0
- package/src/components/Toast/Toast.test.tsx +8 -7
- package/src/components/Toast/Toast.tsx +4 -4
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
- package/src/hooks/useEventTheme.ts +49 -18
- package/src/hooks/usePermissionCache.ts +5 -3
- package/src/hooks/useSecureDataAccess.ts +11 -1
- package/src/hooks/useToast.ts +11 -12
- package/src/providers/services/EventServiceProvider.tsx +15 -8
- package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
- package/src/rbac/audit.test.ts +206 -0
- package/src/rbac/audit.ts +37 -2
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
- package/src/rbac/errors.test.ts +340 -0
- package/src/rbac/hooks/index.ts +9 -0
- package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
- package/src/rbac/hooks/useRoleManagement.ts +255 -0
- package/src/services/AuthService.ts +10 -0
- package/src/services/EventService.ts +111 -50
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +60 -45
- package/src/services/interfaces/IEventService.ts +1 -1
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
- package/src/utils/__tests__/logger.unit.test.ts +398 -0
- package/src/utils/__tests__/validation.unit.test.ts +225 -1
- package/src/utils/file-reference.test.ts +214 -0
- package/dist/chunk-3OGQLOJM.js.map +0 -1
- package/dist/chunk-4OX5PXHX.js.map +0 -1
- package/dist/chunk-5CDJCTOO.js +0 -190
- package/dist/chunk-5YIZFEUQ.js.map +0 -1
- package/dist/chunk-F6QB26OS.js.map +0 -1
- package/dist/chunk-KTHLNIMA.js.map +0 -1
- package/dist/chunk-OO3V7W4H.js.map +0 -1
- package/dist/chunk-ZPXWJA4H.js.map +0 -1
- package/src/rbac/audit-enhanced.ts +0 -351
- /package/dist/{DataTable-3JRLZXER.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
- /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
- /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
- /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
- /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
- /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
- /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
- /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
|
@@ -14,22 +14,14 @@ import {
|
|
|
14
14
|
DefaultPublicErrorFallback
|
|
15
15
|
} from '../PublicErrorBoundary';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const originalError = console.error;
|
|
19
|
-
let mockConsoleError: ReturnType<typeof vi.fn>;
|
|
17
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
20
18
|
|
|
21
19
|
beforeEach(() => {
|
|
22
|
-
|
|
23
|
-
mockConsoleError = vi.fn();
|
|
24
|
-
vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
25
|
-
mockConsoleError(...args);
|
|
26
|
-
// Call original to allow React's error boundary to work
|
|
27
|
-
originalError(...args);
|
|
28
|
-
});
|
|
20
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
29
21
|
});
|
|
30
22
|
|
|
31
23
|
afterEach(() => {
|
|
32
|
-
|
|
24
|
+
consoleErrorSpy.mockRestore();
|
|
33
25
|
});
|
|
34
26
|
|
|
35
27
|
// Component that throws an error
|
|
@@ -80,7 +72,7 @@ describe('[component] PublicErrorBoundary', () => {
|
|
|
80
72
|
);
|
|
81
73
|
|
|
82
74
|
// Check that console.error was called (error boundary should trigger logging)
|
|
83
|
-
expect(
|
|
75
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
84
76
|
// Just verify an error was logged, don't check for exact message
|
|
85
77
|
// as the development mode check might not be active in tests
|
|
86
78
|
});
|
|
@@ -402,8 +402,16 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
402
402
|
(typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown'))
|
|
403
403
|
);
|
|
404
404
|
|
|
405
|
+
// Merge child's className with triggerProps className
|
|
406
|
+
const childClassName = (children as React.ReactElement).props.className;
|
|
407
|
+
const mergedClassName = cn(
|
|
408
|
+
triggerProps.className,
|
|
409
|
+
childClassName
|
|
410
|
+
);
|
|
411
|
+
|
|
405
412
|
return React.cloneElement(children as React.ReactElement, {
|
|
406
413
|
...triggerProps,
|
|
414
|
+
className: mergedClassName,
|
|
407
415
|
children: hasChevron ? childChildren : [
|
|
408
416
|
...childChildren,
|
|
409
417
|
<ChevronDown
|
|
@@ -38,13 +38,14 @@ describe('Toast Component System', () => {
|
|
|
38
38
|
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('renders
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
it('renders without modifying default timeout behaviour', () => {
|
|
42
|
+
expect(() =>
|
|
43
|
+
renderWithProviders(
|
|
44
|
+
<ToastProvider>
|
|
45
|
+
<div>Test Content</div>
|
|
46
|
+
</ToastProvider>
|
|
47
|
+
)
|
|
48
|
+
).not.toThrow();
|
|
48
49
|
expect(screen.getByText('Test Content')).toBeInTheDocument();
|
|
49
50
|
});
|
|
50
51
|
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Customizable positioning and styling
|
|
13
13
|
* - Swipe gestures for dismissal
|
|
14
14
|
* - Keyboard navigation support
|
|
15
|
-
* - Auto-dismiss with default 10 second duration
|
|
15
|
+
* - Auto-dismiss with default 10 second duration managed internally
|
|
16
16
|
* - Action buttons and close functionality
|
|
17
17
|
* - Responsive design
|
|
18
18
|
* - Accessibility compliant
|
|
@@ -296,10 +296,10 @@ export function Toaster() {
|
|
|
296
296
|
<ToastViewport />
|
|
297
297
|
{toasts.map((toast: any) => {
|
|
298
298
|
// Destructure custom properties that shouldn't be passed to DOM
|
|
299
|
-
const { id, title, description, action, dismiss, ...toastProps } = toast;
|
|
300
|
-
|
|
299
|
+
const { id, title, description, action, dismiss, duration, ...toastProps } = toast;
|
|
300
|
+
|
|
301
301
|
return (
|
|
302
|
-
<Toast key={id} {...toastProps}>
|
|
302
|
+
<Toast key={id} {...toastProps} duration={duration}>
|
|
303
303
|
{title && <ToastTitle>{title}</ToastTitle>}
|
|
304
304
|
{description && <ToastDescription>{description}</ToastDescription>}
|
|
305
305
|
{action && action}
|
|
@@ -269,6 +269,311 @@ describe('usePublicEvent - Simple Tests', () => {
|
|
|
269
269
|
});
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
+
describe('Refetch Functionality', () => {
|
|
273
|
+
it('refetches data when refetch is called', async () => {
|
|
274
|
+
const mockEventData = {
|
|
275
|
+
event_id: '123',
|
|
276
|
+
event_name: 'Test Event',
|
|
277
|
+
event_date: '2024-01-01',
|
|
278
|
+
event_venue: 'Test Venue',
|
|
279
|
+
event_participants: 100,
|
|
280
|
+
event_colours: { primary: '#000000' },
|
|
281
|
+
organisation_id: 'org-123',
|
|
282
|
+
event_days: 1,
|
|
283
|
+
event_typicalunit: 'km',
|
|
284
|
+
event_rounddown: false,
|
|
285
|
+
event_youthmultiplier: 1.0,
|
|
286
|
+
event_catering_email: 'test@example.com',
|
|
287
|
+
event_news: 'Test news',
|
|
288
|
+
event_billing: 'Test billing',
|
|
289
|
+
event_footer: 'Test footer',
|
|
290
|
+
event_email: 'event@example.com',
|
|
291
|
+
event_logo: null
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
295
|
+
data: [mockEventData],
|
|
296
|
+
error: null
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const { result } = renderHook(() => usePublicEvent('test-event', { enableCache: true }));
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(result.current.isLoading).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const firstCallCount = mockSupabaseClient.rpc.mock.calls.length;
|
|
306
|
+
|
|
307
|
+
// Update mock to return different data
|
|
308
|
+
const updatedEventData = { ...mockEventData, event_name: 'Updated Event' };
|
|
309
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
310
|
+
data: [updatedEventData],
|
|
311
|
+
error: null
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await result.current.refetch();
|
|
315
|
+
|
|
316
|
+
await waitFor(() => {
|
|
317
|
+
expect(result.current.event?.event_name).toBe('Updated Event');
|
|
318
|
+
}, { timeout: 2000 });
|
|
319
|
+
|
|
320
|
+
// Should have made another call
|
|
321
|
+
expect(mockSupabaseClient.rpc.mock.calls.length).toBeGreaterThan(firstCallCount);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('clears cache before refetching', async () => {
|
|
325
|
+
const mockEventData = {
|
|
326
|
+
event_id: '123',
|
|
327
|
+
event_name: 'Test Event',
|
|
328
|
+
event_date: '2024-01-01',
|
|
329
|
+
event_venue: 'Test Venue',
|
|
330
|
+
event_participants: 100,
|
|
331
|
+
event_colours: { primary: '#000000' },
|
|
332
|
+
organisation_id: 'org-123',
|
|
333
|
+
event_days: 1,
|
|
334
|
+
event_typicalunit: 'km',
|
|
335
|
+
event_rounddown: false,
|
|
336
|
+
event_youthmultiplier: 1.0,
|
|
337
|
+
event_catering_email: 'test@example.com',
|
|
338
|
+
event_news: 'Test news',
|
|
339
|
+
event_billing: 'Test billing',
|
|
340
|
+
event_footer: 'Test footer',
|
|
341
|
+
event_email: 'event@example.com',
|
|
342
|
+
event_logo: null
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
346
|
+
data: [mockEventData],
|
|
347
|
+
error: null
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const { result } = renderHook(() => usePublicEvent('test-event', { enableCache: true }));
|
|
351
|
+
|
|
352
|
+
await waitFor(() => {
|
|
353
|
+
expect(result.current.isLoading).toBe(false);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Verify cache exists
|
|
357
|
+
const statsBefore = getPublicEventCacheStats();
|
|
358
|
+
expect(statsBefore.size).toBeGreaterThan(0);
|
|
359
|
+
|
|
360
|
+
await result.current.refetch();
|
|
361
|
+
|
|
362
|
+
// Cache should be cleared and then repopulated
|
|
363
|
+
await waitFor(() => {
|
|
364
|
+
expect(result.current.event).toBeDefined();
|
|
365
|
+
}, { timeout: 2000 });
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('Caching Options', () => {
|
|
370
|
+
it('respects cacheTtl option', async () => {
|
|
371
|
+
const mockEventData = {
|
|
372
|
+
event_id: '123',
|
|
373
|
+
event_name: 'Test Event',
|
|
374
|
+
event_date: '2024-01-01',
|
|
375
|
+
event_venue: 'Test Venue',
|
|
376
|
+
event_participants: 100,
|
|
377
|
+
event_colours: { primary: '#000000' },
|
|
378
|
+
organisation_id: 'org-123',
|
|
379
|
+
event_days: 1,
|
|
380
|
+
event_typicalunit: 'km',
|
|
381
|
+
event_rounddown: false,
|
|
382
|
+
event_youthmultiplier: 1.0,
|
|
383
|
+
event_catering_email: 'test@example.com',
|
|
384
|
+
event_news: 'Test news',
|
|
385
|
+
event_billing: 'Test billing',
|
|
386
|
+
event_footer: 'Test footer',
|
|
387
|
+
event_email: 'event@example.com',
|
|
388
|
+
event_logo: null
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
392
|
+
data: [mockEventData],
|
|
393
|
+
error: null
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const { result } = renderHook(() => usePublicEvent('test-event', {
|
|
397
|
+
enableCache: true,
|
|
398
|
+
cacheTtl: 1000 // 1 second
|
|
399
|
+
}));
|
|
400
|
+
|
|
401
|
+
await waitFor(() => {
|
|
402
|
+
expect(result.current.isLoading).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
expect(result.current.event).toBeTruthy();
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('disables caching when enableCache is false', async () => {
|
|
409
|
+
const mockEventData = {
|
|
410
|
+
event_id: '123',
|
|
411
|
+
event_name: 'Test Event',
|
|
412
|
+
event_date: '2024-01-01',
|
|
413
|
+
event_venue: 'Test Venue',
|
|
414
|
+
event_participants: 100,
|
|
415
|
+
event_colours: { primary: '#000000' },
|
|
416
|
+
organisation_id: 'org-123',
|
|
417
|
+
event_days: 1,
|
|
418
|
+
event_typicalunit: 'km',
|
|
419
|
+
event_rounddown: false,
|
|
420
|
+
event_youthmultiplier: 1.0,
|
|
421
|
+
event_catering_email: 'test@example.com',
|
|
422
|
+
event_news: 'Test news',
|
|
423
|
+
event_billing: 'Test billing',
|
|
424
|
+
event_footer: 'Test footer',
|
|
425
|
+
event_email: 'event@example.com',
|
|
426
|
+
event_logo: null
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
mockSupabaseClient.rpc.mockResolvedValue({
|
|
430
|
+
data: [mockEventData],
|
|
431
|
+
error: null
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const { result, rerender } = renderHook(() => usePublicEvent('test-event', {
|
|
435
|
+
enableCache: false
|
|
436
|
+
}));
|
|
437
|
+
|
|
438
|
+
await waitFor(() => {
|
|
439
|
+
expect(result.current.isLoading).toBe(false);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const firstCallCount = mockSupabaseClient.rpc.mock.calls.length;
|
|
443
|
+
|
|
444
|
+
// Rerender - should make another call since caching is disabled
|
|
445
|
+
rerender();
|
|
446
|
+
|
|
447
|
+
await waitFor(() => {
|
|
448
|
+
expect(result.current.event).toBeTruthy();
|
|
449
|
+
}, { timeout: 2000 });
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe('Error Handling', () => {
|
|
454
|
+
it('handles RPC errors that are not schema cache issues', async () => {
|
|
455
|
+
const rpcError = { message: 'Permission denied', code: 'PGRST301' };
|
|
456
|
+
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
457
|
+
data: null,
|
|
458
|
+
error: rpcError
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
462
|
+
|
|
463
|
+
await waitFor(() => {
|
|
464
|
+
expect(result.current.isLoading).toBe(false);
|
|
465
|
+
}, { timeout: 2000 });
|
|
466
|
+
|
|
467
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
468
|
+
expect(result.current.error?.message).toBe('Permission denied');
|
|
469
|
+
expect(result.current.event).toBe(null);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('handles exceptions during event fetch', async () => {
|
|
473
|
+
const error = new Error('Network timeout');
|
|
474
|
+
// Mock RPC to reject, and table access to also fail
|
|
475
|
+
mockSupabaseClient.rpc.mockRejectedValue(error);
|
|
476
|
+
mockSupabaseClient.from.mockReturnValue({
|
|
477
|
+
select: vi.fn().mockReturnThis(),
|
|
478
|
+
eq: vi.fn().mockReturnThis(),
|
|
479
|
+
not: vi.fn().mockReturnThis(),
|
|
480
|
+
limit: vi.fn().mockReturnThis(),
|
|
481
|
+
single: vi.fn().mockRejectedValue(error)
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
485
|
+
|
|
486
|
+
await waitFor(() => {
|
|
487
|
+
expect(result.current.isLoading).toBe(false);
|
|
488
|
+
}, { timeout: 2000 });
|
|
489
|
+
|
|
490
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
491
|
+
expect(result.current.error?.message).toBe('Network timeout');
|
|
492
|
+
expect(result.current.event).toBe(null);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('handles non-Error exceptions', async () => {
|
|
496
|
+
const stringError = 'String error';
|
|
497
|
+
// Mock RPC to reject, and table access to also fail
|
|
498
|
+
mockSupabaseClient.rpc.mockRejectedValue(stringError);
|
|
499
|
+
mockSupabaseClient.from.mockReturnValue({
|
|
500
|
+
select: vi.fn().mockReturnThis(),
|
|
501
|
+
eq: vi.fn().mockReturnThis(),
|
|
502
|
+
not: vi.fn().mockReturnThis(),
|
|
503
|
+
limit: vi.fn().mockReturnThis(),
|
|
504
|
+
single: vi.fn().mockRejectedValue(stringError)
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
508
|
+
|
|
509
|
+
await waitFor(() => {
|
|
510
|
+
expect(result.current.isLoading).toBe(false);
|
|
511
|
+
}, { timeout: 2000 });
|
|
512
|
+
|
|
513
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
514
|
+
expect(result.current.error?.message).toBe('Unknown error occurred');
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
describe('Parameter Changes', () => {
|
|
519
|
+
it('refetches when eventCode changes', async () => {
|
|
520
|
+
const mockEventData1 = {
|
|
521
|
+
event_id: '123',
|
|
522
|
+
event_name: 'Event 1',
|
|
523
|
+
event_date: '2024-01-01',
|
|
524
|
+
event_venue: 'Venue 1',
|
|
525
|
+
event_participants: 100,
|
|
526
|
+
event_colours: { primary: '#000000' },
|
|
527
|
+
organisation_id: 'org-123',
|
|
528
|
+
event_days: 1,
|
|
529
|
+
event_typicalunit: 'km',
|
|
530
|
+
event_rounddown: false,
|
|
531
|
+
event_youthmultiplier: 1.0,
|
|
532
|
+
event_catering_email: 'test@example.com',
|
|
533
|
+
event_news: 'Test news',
|
|
534
|
+
event_billing: 'Test billing',
|
|
535
|
+
event_footer: 'Test footer',
|
|
536
|
+
event_email: 'event@example.com',
|
|
537
|
+
event_logo: null
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const mockEventData2 = {
|
|
541
|
+
...mockEventData1,
|
|
542
|
+
event_id: '456',
|
|
543
|
+
event_name: 'Event 2'
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
mockSupabaseClient.rpc
|
|
547
|
+
.mockResolvedValueOnce({
|
|
548
|
+
data: [mockEventData1],
|
|
549
|
+
error: null
|
|
550
|
+
})
|
|
551
|
+
.mockResolvedValueOnce({
|
|
552
|
+
data: [mockEventData2],
|
|
553
|
+
error: null
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const { result, rerender } = renderHook(
|
|
557
|
+
({ eventCode }) => usePublicEvent(eventCode),
|
|
558
|
+
{ initialProps: { eventCode: 'event-1' } }
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
await waitFor(() => {
|
|
562
|
+
expect(result.current.isLoading).toBe(false);
|
|
563
|
+
}, { timeout: 2000 });
|
|
564
|
+
|
|
565
|
+
expect(result.current.event?.event_name).toBe('Event 1');
|
|
566
|
+
|
|
567
|
+
rerender({ eventCode: 'event-2' });
|
|
568
|
+
|
|
569
|
+
await waitFor(() => {
|
|
570
|
+
expect(result.current.isLoading).toBe(false);
|
|
571
|
+
}, { timeout: 2000 });
|
|
572
|
+
|
|
573
|
+
expect(result.current.event?.event_name).toBe('Event 2');
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
272
577
|
describe('Edge Cases', () => {
|
|
273
578
|
it('should handle null event data from RPC', async () => {
|
|
274
579
|
mockSupabaseClient.rpc.mockResolvedValueOnce({
|
|
@@ -280,7 +585,7 @@ describe('usePublicEvent - Simple Tests', () => {
|
|
|
280
585
|
|
|
281
586
|
await waitFor(() => {
|
|
282
587
|
expect(result.current.isLoading).toBe(false);
|
|
283
|
-
});
|
|
588
|
+
}, { timeout: 2000 });
|
|
284
589
|
|
|
285
590
|
expect(result.current.event).toBe(null);
|
|
286
591
|
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
@@ -296,7 +601,7 @@ describe('usePublicEvent - Simple Tests', () => {
|
|
|
296
601
|
|
|
297
602
|
await waitFor(() => {
|
|
298
603
|
expect(result.current.isLoading).toBe(false);
|
|
299
|
-
});
|
|
604
|
+
}, { timeout: 2000 });
|
|
300
605
|
|
|
301
606
|
expect(result.current.event).toBe(null);
|
|
302
607
|
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
@@ -312,10 +617,69 @@ describe('usePublicEvent - Simple Tests', () => {
|
|
|
312
617
|
|
|
313
618
|
await waitFor(() => {
|
|
314
619
|
expect(result.current.isLoading).toBe(false);
|
|
315
|
-
});
|
|
620
|
+
}, { timeout: 2000 });
|
|
316
621
|
|
|
317
622
|
expect(result.current.event).toBe(null);
|
|
318
623
|
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
319
624
|
});
|
|
625
|
+
|
|
626
|
+
it('handles missing Supabase environment variables', () => {
|
|
627
|
+
// Mock usePublicPageContext to return null environment
|
|
628
|
+
vi.mocked(usePublicPageContext).mockReturnValue({
|
|
629
|
+
environment: {
|
|
630
|
+
supabaseUrl: null,
|
|
631
|
+
supabaseKey: null
|
|
632
|
+
}
|
|
633
|
+
} as any);
|
|
634
|
+
|
|
635
|
+
// Mock environment variables to be undefined
|
|
636
|
+
Object.defineProperty(import.meta, 'env', {
|
|
637
|
+
value: {},
|
|
638
|
+
writable: true
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
642
|
+
|
|
643
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
644
|
+
|
|
645
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
646
|
+
'[usePublicEvent] Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment.'
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
// Should still initialize but with error
|
|
650
|
+
expect(result.current.isLoading).toBe(false);
|
|
651
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
652
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
653
|
+
|
|
654
|
+
consoleSpy.mockRestore();
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('handles server-side rendering (window undefined)', () => {
|
|
658
|
+
// Test that the hook handles missing window by checking the implementation
|
|
659
|
+
// The hook checks `typeof window === 'undefined'` and returns null supabase client
|
|
660
|
+
// We can't actually delete window in the test environment as React needs it
|
|
661
|
+
// Instead, we test the behavior when supabase client is null (which happens when window is undefined)
|
|
662
|
+
|
|
663
|
+
// Mock usePublicPageContext to return null environment
|
|
664
|
+
vi.mocked(usePublicPageContext).mockReturnValue({
|
|
665
|
+
environment: {
|
|
666
|
+
supabaseUrl: null,
|
|
667
|
+
supabaseKey: null
|
|
668
|
+
}
|
|
669
|
+
} as any);
|
|
670
|
+
|
|
671
|
+
// Mock environment variables to be undefined
|
|
672
|
+
Object.defineProperty(import.meta, 'env', {
|
|
673
|
+
value: {},
|
|
674
|
+
writable: true
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
678
|
+
|
|
679
|
+
// When supabase client is null (as it would be in SSR), the hook should handle it gracefully
|
|
680
|
+
expect(result.current.isLoading).toBe(false);
|
|
681
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
682
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
683
|
+
});
|
|
320
684
|
});
|
|
321
685
|
});
|