@jmruthers/pace-core 0.6.2 → 0.6.3

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 (299) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/cursor-rules/00-pace-core-compliance.mdc +34 -2
  3. package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
  4. package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
  5. package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
  6. package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
  7. package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
  11. package/dist/chunk-2T2IG7T7.js.map +1 -0
  12. package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
  13. package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
  14. package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
  15. package/dist/chunk-CNCQDFLN.js.map +1 -0
  16. package/dist/chunk-DGUM43GV.js +11 -0
  17. package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
  18. package/dist/chunk-DWUBLJJM.js.map +1 -0
  19. package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
  20. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  21. package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
  22. package/dist/chunk-PQBSKX33.js.map +1 -0
  23. package/dist/chunk-QRPVRXYT.js +226 -0
  24. package/dist/chunk-QRPVRXYT.js.map +1 -0
  25. package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
  26. package/dist/chunk-RWEBCB47.js.map +1 -0
  27. package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
  28. package/dist/chunk-YDQHOZNA.js.map +1 -0
  29. package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
  30. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-3JNZKUTX.js +9 -0
  34. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  35. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  36. package/dist/hooks.d.ts +2 -2
  37. package/dist/hooks.js +7 -6
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +7 -7
  40. package/dist/index.js +21 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.d.ts +3 -3
  43. package/dist/providers.js +4 -3
  44. package/dist/rbac/index.d.ts +67 -27
  45. package/dist/rbac/index.js +15 -8
  46. package/dist/styles/index.js +1 -1
  47. package/dist/theming/runtime.js +1 -1
  48. package/dist/types.js +1 -1
  49. package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
  50. package/dist/utils.js +5 -7
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/README.md +14 -16
  53. package/docs/api/modules.md +3796 -2513
  54. package/docs/components/context-selector.md +126 -0
  55. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  56. package/docs/pace-mint-fix-auto-selection.md +218 -0
  57. package/docs/pace-mint-rbac-setup.md +391 -0
  58. package/docs/rbac/secure-client-protection.md +330 -0
  59. package/package.json +3 -3
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +559 -28
  62. package/scripts/audit/core/checks/documentation.cjs +68 -3
  63. package/scripts/audit/core/checks/environment.cjs +2 -14
  64. package/scripts/audit/core/checks/error-handling.cjs +47 -6
  65. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  66. package/src/components/ContextSelector/index.ts +3 -0
  67. package/src/components/DataTable/components/RowComponent.tsx +19 -19
  68. package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
  69. package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
  70. package/src/components/Dialog/Dialog.tsx +29 -1
  71. package/src/components/FileDisplay/FileDisplay.tsx +42 -10
  72. package/src/components/Header/Header.test.tsx +43 -73
  73. package/src/components/Header/Header.tsx +44 -45
  74. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  75. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  76. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  77. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
  78. package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
  79. package/src/components/PaceAppLayout/README.md +14 -17
  80. package/src/components/PaceAppLayout/test-setup.tsx +2 -2
  81. package/src/components/index.ts +5 -5
  82. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  83. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  84. package/src/hooks/useAppConfig.ts +15 -30
  85. package/src/hooks/useFileDisplay.ts +77 -50
  86. package/src/index.ts +4 -5
  87. package/src/providers/services/AuthServiceProvider.tsx +17 -7
  88. package/src/providers/services/EventServiceProvider.tsx +33 -5
  89. package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
  90. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  91. package/src/rbac/adapters.tsx +2 -2
  92. package/src/rbac/api.test.ts +59 -51
  93. package/src/rbac/api.ts +178 -132
  94. package/src/rbac/components/PagePermissionGuard.tsx +38 -10
  95. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  96. package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
  97. package/src/rbac/hooks/permissions/useCan.ts +41 -11
  98. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
  99. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
  100. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
  101. package/src/rbac/hooks/useCan.test.ts +0 -9
  102. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  103. package/src/rbac/hooks/useRBAC.ts +36 -37
  104. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  105. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  106. package/src/rbac/hooks/useSecureSupabase.ts +7 -7
  107. package/src/rbac/index.ts +7 -0
  108. package/src/rbac/secureClient.test.ts +22 -18
  109. package/src/rbac/secureClient.ts +103 -16
  110. package/src/rbac/security.ts +0 -17
  111. package/src/rbac/types.ts +1 -0
  112. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  113. package/src/rbac/utils/clientSecurity.ts +93 -0
  114. package/src/rbac/utils/contextValidator.ts +77 -168
  115. package/src/services/AuthService.ts +39 -7
  116. package/src/services/EventService.ts +186 -54
  117. package/src/services/OrganisationService.ts +81 -14
  118. package/src/services/__tests__/EventService.test.ts +1 -2
  119. package/src/services/base/BaseService.ts +3 -0
  120. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  121. package/dist/chunk-24UVZUZG.js.map +0 -1
  122. package/dist/chunk-3XC4CPTD.js.map +0 -1
  123. package/dist/chunk-6J4GEEJR.js.map +0 -1
  124. package/dist/chunk-7D4SUZUM.js +0 -38
  125. package/dist/chunk-EHMR7VYL.js.map +0 -1
  126. package/dist/chunk-NECFR5MM.js.map +0 -1
  127. package/dist/chunk-SFZUDBL5.js.map +0 -1
  128. package/dist/chunk-XWQCNGTQ.js.map +0 -1
  129. package/docs/api/classes/ColumnFactory.md +0 -243
  130. package/docs/api/classes/InvalidScopeError.md +0 -73
  131. package/docs/api/classes/Logger.md +0 -178
  132. package/docs/api/classes/MissingUserContextError.md +0 -66
  133. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  134. package/docs/api/classes/PermissionDeniedError.md +0 -73
  135. package/docs/api/classes/RBACAuditManager.md +0 -297
  136. package/docs/api/classes/RBACCache.md +0 -322
  137. package/docs/api/classes/RBACEngine.md +0 -171
  138. package/docs/api/classes/RBACError.md +0 -76
  139. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  140. package/docs/api/classes/SecureSupabaseClient.md +0 -163
  141. package/docs/api/classes/StorageUtils.md +0 -328
  142. package/docs/api/enums/FileCategory.md +0 -184
  143. package/docs/api/enums/LogLevel.md +0 -54
  144. package/docs/api/enums/RBACErrorCode.md +0 -228
  145. package/docs/api/enums/RPCFunction.md +0 -118
  146. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  147. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  148. package/docs/api/interfaces/AggregateConfig.md +0 -43
  149. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  150. package/docs/api/interfaces/AvatarProps.md +0 -128
  151. package/docs/api/interfaces/BadgeProps.md +0 -34
  152. package/docs/api/interfaces/ButtonProps.md +0 -56
  153. package/docs/api/interfaces/CalendarProps.md +0 -73
  154. package/docs/api/interfaces/CardProps.md +0 -69
  155. package/docs/api/interfaces/ColorPalette.md +0 -7
  156. package/docs/api/interfaces/ColorShade.md +0 -66
  157. package/docs/api/interfaces/ComplianceResult.md +0 -30
  158. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  159. package/docs/api/interfaces/DataRecord.md +0 -11
  160. package/docs/api/interfaces/DataTableAction.md +0 -252
  161. package/docs/api/interfaces/DataTableColumn.md +0 -504
  162. package/docs/api/interfaces/DataTableProps.md +0 -625
  163. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  164. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  165. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  166. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  167. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  168. package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
  169. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
  170. package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
  171. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  172. package/docs/api/interfaces/ExportColumn.md +0 -90
  173. package/docs/api/interfaces/ExportOptions.md +0 -126
  174. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  175. package/docs/api/interfaces/FileMetadata.md +0 -129
  176. package/docs/api/interfaces/FileReference.md +0 -118
  177. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  178. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  179. package/docs/api/interfaces/FileUploadProps.md +0 -296
  180. package/docs/api/interfaces/FooterProps.md +0 -107
  181. package/docs/api/interfaces/FormFieldProps.md +0 -166
  182. package/docs/api/interfaces/FormProps.md +0 -113
  183. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  184. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  185. package/docs/api/interfaces/InputProps.md +0 -56
  186. package/docs/api/interfaces/LabelProps.md +0 -107
  187. package/docs/api/interfaces/LoggerConfig.md +0 -62
  188. package/docs/api/interfaces/LoginFormProps.md +0 -187
  189. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  190. package/docs/api/interfaces/NavigationContextType.md +0 -164
  191. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  192. package/docs/api/interfaces/NavigationItem.md +0 -120
  193. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  194. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  195. package/docs/api/interfaces/Organisation.md +0 -140
  196. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  197. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  198. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  199. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  200. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
  201. package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
  202. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  203. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  204. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  205. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  206. package/docs/api/interfaces/PaletteData.md +0 -41
  207. package/docs/api/interfaces/ParsedAddress.md +0 -120
  208. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  209. package/docs/api/interfaces/ProgressProps.md +0 -42
  210. package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
  211. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  212. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  213. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
  214. package/docs/api/interfaces/QuickFix.md +0 -52
  215. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  216. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  217. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  218. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  219. package/docs/api/interfaces/RBACConfig.md +0 -133
  220. package/docs/api/interfaces/RBACContext.md +0 -52
  221. package/docs/api/interfaces/RBACLogger.md +0 -112
  222. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  223. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  224. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  225. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  226. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  227. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  228. package/docs/api/interfaces/RBACResult.md +0 -58
  229. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  230. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  231. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  232. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  233. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  234. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  235. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  236. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  237. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  238. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  239. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  240. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  241. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  242. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  243. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  244. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  245. package/docs/api/interfaces/RouteConfig.md +0 -134
  246. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  247. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  248. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  249. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  250. package/docs/api/interfaces/SetupIssue.md +0 -41
  251. package/docs/api/interfaces/StorageConfig.md +0 -41
  252. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  253. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  254. package/docs/api/interfaces/StorageListOptions.md +0 -99
  255. package/docs/api/interfaces/StorageListResult.md +0 -41
  256. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  257. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  258. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  259. package/docs/api/interfaces/StyleImport.md +0 -19
  260. package/docs/api/interfaces/SwitchProps.md +0 -34
  261. package/docs/api/interfaces/TabsContentProps.md +0 -9
  262. package/docs/api/interfaces/TabsListProps.md +0 -9
  263. package/docs/api/interfaces/TabsProps.md +0 -9
  264. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  265. package/docs/api/interfaces/TextareaProps.md +0 -53
  266. package/docs/api/interfaces/ToastActionElement.md +0 -12
  267. package/docs/api/interfaces/ToastProps.md +0 -9
  268. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
  269. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
  270. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  271. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  272. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
  273. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  274. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  275. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
  276. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  277. package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
  278. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  279. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
  280. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
  281. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  282. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  283. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  284. package/docs/api/interfaces/UserEventAccess.md +0 -121
  285. package/docs/api/interfaces/UserMenuProps.md +0 -88
  286. package/docs/api/interfaces/UserProfile.md +0 -63
  287. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  288. package/src/components/EventSelector/EventSelector.tsx +0 -423
  289. package/src/components/EventSelector/index.ts +0 -3
  290. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  291. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
  292. package/src/components/OrganisationSelector/index.ts +0 -9
  293. /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  298. /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -1,720 +0,0 @@
1
- /**
2
- * @file EventSelector Component Tests
3
- * @description Comprehensive test suite for EventSelector component
4
- * @package @jmruthers/pace-core
5
- * @module Components/EventSelector
6
- * @since 0.1.0
7
- *
8
- * Comprehensive test suite for EventSelector component following testing guidelines.
9
- * Tests cover all major functionality, edge cases, and user interactions.
10
- */
11
-
12
- import React from 'react';
13
- import { screen, waitFor, act } from '@testing-library/react';
14
- import userEvent from '@testing-library/user-event';
15
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
16
- import { EventSelector, EventSelectorProps } from './EventSelector';
17
- import { renderWithProviders } from '../../__tests__/helpers/test-utils';
18
- import type { Event } from '../../types';
19
-
20
- // Mock the useEvents hook
21
- const mockUseEvents = vi.fn();
22
- vi.mock('../../hooks/useEvents', () => ({
23
- useEvents: () => mockUseEvents(),
24
- }));
25
-
26
- // Mock child components
27
- let mockOnValueChange: ((value: string) => void) | null = null;
28
-
29
- vi.mock('../Select', () => ({
30
- Select: ({ children, value, onValueChange, className }: any) => {
31
- // Store the onValueChange callback for SelectItem to use
32
- mockOnValueChange = onValueChange;
33
- return (
34
- <div data-testid="select" data-value={value} className={className}>
35
- {children}
36
- </div>
37
- );
38
- },
39
- SelectContent: ({ children }: any) => (
40
- <div data-testid="select-content">{children}</div>
41
- ),
42
- SelectItem: ({ children, value, className }: any) => (
43
- <div
44
- data-testid={`select-item-${value}`}
45
- className={className}
46
- onClick={() => mockOnValueChange?.(value)}
47
- >
48
- {children}
49
- </div>
50
- ),
51
- SelectTrigger: ({ children, className }: any) => (
52
- <button data-testid="select-trigger" className={className}>
53
- {children}
54
- </button>
55
- ),
56
- SelectValue: ({ placeholder, children }: any) => (
57
- <span data-testid="select-value">
58
- {children || placeholder}
59
- </span>
60
- ),
61
- }));
62
-
63
- vi.mock('../Alert/Alert', () => ({
64
- Alert: ({ children, variant }: any) => (
65
- <div data-testid="alert" data-variant={variant}>
66
- {children}
67
- </div>
68
- ),
69
- AlertDescription: ({ children, className }: any) => (
70
- <div data-testid="alert-description" className={className}>
71
- {children}
72
- </div>
73
- ),
74
- }));
75
-
76
- vi.mock('../Button/Button', () => ({
77
- Button: ({ children, onClick, variant, size, className }: any) => (
78
- <button
79
- data-testid="button"
80
- onClick={onClick}
81
- data-variant={variant}
82
- data-size={size}
83
- className={className}
84
- >
85
- {children}
86
- </button>
87
- ),
88
- }));
89
-
90
- vi.mock('../LoadingSpinner/LoadingSpinner', () => ({
91
- LoadingSpinner: ({ size }: any) => (
92
- <div data-testid="loading-spinner" data-size={size}>Loading...</div>
93
- ),
94
- }));
95
-
96
- vi.mock('lucide-react', () => ({
97
- RefreshCw: () => <span data-testid="icon-refresh">refresh</span>,
98
- AlertCircle: () => <span data-testid="icon-alert">alert</span>,
99
- Lock: () => <span data-testid="icon-lock">lock</span>,
100
- Calendar: () => <span data-testid="icon-calendar">calendar</span>,
101
- Star: () => <span data-testid="icon-star">star</span>,
102
- }));
103
-
104
- // Test data
105
- const mockEvents: Event[] = [
106
- {
107
- id: 'event-1',
108
- event_id: 'event-1',
109
- event_name: 'Spring Festival 2024',
110
- event_date: '2024-03-15T00:00:00Z',
111
- event_venue: 'City Park',
112
- organisation_id: 'org-1',
113
- created_at: '2023-01-01T00:00:00Z',
114
- updated_at: '2023-01-01T00:00:00Z',
115
- },
116
- {
117
- id: 'event-2',
118
- event_id: 'event-2',
119
- event_name: 'Summer Conference',
120
- event_date: '2024-06-20T00:00:00Z',
121
- event_venue: 'Convention Center',
122
- organisation_id: 'org-1',
123
- created_at: '2023-01-02T00:00:00Z',
124
- updated_at: '2023-01-02T00:00:00Z',
125
- },
126
- {
127
- id: 'event-3',
128
- event_id: 'event-3',
129
- event_name: 'Winter Gala',
130
- event_date: '2024-12-01T00:00:00Z',
131
- event_venue: 'Grand Hotel',
132
- organisation_id: 'org-1',
133
- created_at: '2023-01-03T00:00:00Z',
134
- updated_at: '2023-01-03T00:00:00Z',
135
- },
136
- ];
137
-
138
- const mockSelectedEvent = mockEvents[0];
139
-
140
- const defaultMockContext = {
141
- events: mockEvents,
142
- selectedEvent: mockSelectedEvent,
143
- isLoading: false,
144
- error: null,
145
- setSelectedEvent: vi.fn(),
146
- refreshEvents: vi.fn().mockResolvedValue(undefined),
147
- };
148
-
149
- describe('EventSelector Component', () => {
150
- beforeEach(() => {
151
- vi.clearAllMocks();
152
-
153
- // Setup the default mock context
154
- mockUseEvents.mockReturnValue(defaultMockContext);
155
- });
156
-
157
- afterEach(() => {
158
- vi.useRealTimers();
159
- mockOnValueChange = null;
160
- });
161
-
162
- describe('Rendering', () => {
163
- it('renders with default props', () => {
164
- renderWithProviders(<EventSelector />);
165
-
166
- expect(screen.getByTestId('select')).toBeInTheDocument();
167
- expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
168
- });
169
-
170
- it('renders with custom placeholder', () => {
171
- mockUseEvents.mockReturnValue({
172
- ...defaultMockContext,
173
- selectedEvent: null, // No selected event should show placeholder
174
- });
175
-
176
- renderWithProviders(<EventSelector placeholder="Choose an event" />);
177
-
178
- expect(screen.getByTestId('select-value')).toBeInTheDocument();
179
- });
180
-
181
- it('renders with custom className', () => {
182
- renderWithProviders(<EventSelector className="custom-class" />);
183
-
184
- const select = screen.getByTestId('select');
185
- expect(select).toBeInTheDocument();
186
- expect(select).toBeVisible();
187
- });
188
-
189
- it('renders all events in dropdown', () => {
190
- renderWithProviders(<EventSelector />);
191
-
192
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
193
- });
194
- });
195
-
196
- describe('Loading State', () => {
197
- it('shows loading spinner when isLoading is true', () => {
198
- mockUseEvents.mockReturnValue({
199
- ...defaultMockContext,
200
- isLoading: true,
201
- });
202
-
203
- renderWithProviders(<EventSelector />);
204
-
205
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
206
- expect(screen.getByText('Loading events...')).toBeInTheDocument();
207
- });
208
- });
209
-
210
- describe('Error State', () => {
211
- it('shows error message when error occurs', () => {
212
- const error = new Error('Failed to load events');
213
- mockUseEvents.mockReturnValue({
214
- ...defaultMockContext,
215
- error,
216
- });
217
-
218
- renderWithProviders(<EventSelector />);
219
-
220
- expect(screen.getByTestId('alert')).toBeInTheDocument();
221
- expect(screen.getByTestId('alert')).toHaveAttribute('data-variant', 'destructive');
222
- expect(screen.getByText('Failed to load events')).toBeInTheDocument();
223
- });
224
-
225
- it('shows retry button when error and showRetryButton is true', () => {
226
- const error = new Error('Failed to load events');
227
- mockUseEvents.mockReturnValue({
228
- ...defaultMockContext,
229
- error,
230
- });
231
-
232
- renderWithProviders(<EventSelector showRetryButton={true} />);
233
-
234
- expect(screen.getByTestId('button')).toBeInTheDocument();
235
- expect(screen.getByText('Retry')).toBeInTheDocument();
236
- });
237
-
238
- it('hides retry button when showRetryButton is false', () => {
239
- const error = new Error('Failed to load events');
240
- mockUseEvents.mockReturnValue({
241
- ...defaultMockContext,
242
- error,
243
- });
244
-
245
- renderWithProviders(<EventSelector showRetryButton={false} />);
246
-
247
- expect(screen.queryByTestId('button')).not.toBeInTheDocument();
248
- });
249
-
250
- it('calls refreshEvents when retry button is clicked', async () => {
251
- const refreshEventsMock = vi.fn().mockResolvedValue(undefined);
252
- const error = new Error('Failed to load events');
253
-
254
- mockUseEvents.mockReturnValue({
255
- ...defaultMockContext,
256
- error,
257
- refreshEvents: refreshEventsMock,
258
- });
259
-
260
- renderWithProviders(<EventSelector showRetryButton={true} />);
261
-
262
- const retryButton = screen.getByTestId('button');
263
- const user = userEvent.setup();
264
- await user.click(retryButton);
265
-
266
- expect(refreshEventsMock).toHaveBeenCalledTimes(1);
267
- });
268
- });
269
-
270
- describe('No Events State', () => {
271
- it('shows no events message when showNoEventsMessage is true and no events', () => {
272
- mockUseEvents.mockReturnValue({
273
- ...defaultMockContext,
274
- events: [],
275
- });
276
-
277
- renderWithProviders(<EventSelector showNoEventsMessage={true} />);
278
-
279
- expect(screen.getByTestId('alert')).toBeInTheDocument();
280
- expect(screen.getByText('No events available.')).toBeInTheDocument();
281
- });
282
-
283
- it('hides message when showNoEventsMessage is false and no events', () => {
284
- mockUseEvents.mockReturnValue({
285
- ...defaultMockContext,
286
- events: [],
287
- });
288
-
289
- const { container } = renderWithProviders(<EventSelector showNoEventsMessage={false} />);
290
-
291
- expect(container.firstChild).toBeNull();
292
- });
293
-
294
- it('shows refresh button when no events and showRetryButton is true', () => {
295
- mockUseEvents.mockReturnValue({
296
- ...defaultMockContext,
297
- events: [],
298
- });
299
-
300
- renderWithProviders(<EventSelector showRetryButton={true} />);
301
-
302
- expect(screen.getByTestId('button')).toBeInTheDocument();
303
- expect(screen.getByText('Refresh')).toBeInTheDocument();
304
- });
305
- });
306
-
307
- describe('Event Selection', () => {
308
- it('calls onEventChange when an event is selected', async () => {
309
- const onEventChangeMock = vi.fn();
310
-
311
- renderWithProviders(
312
- <EventSelector onEventChange={onEventChangeMock} />
313
- );
314
-
315
- // Find and click an event item
316
- const user = userEvent.setup();
317
- const eventItem = screen.getByTestId('select-item-event-2');
318
- await user.click(eventItem);
319
-
320
- expect(onEventChangeMock).toHaveBeenCalledTimes(1);
321
- });
322
-
323
- it('calls setSelectedEvent when an event is selected', async () => {
324
- const setSelectedEventMock = vi.fn();
325
-
326
- mockUseEvents.mockReturnValue({
327
- ...defaultMockContext,
328
- setSelectedEvent: setSelectedEventMock,
329
- });
330
-
331
- renderWithProviders(<EventSelector />);
332
-
333
- const user = userEvent.setup();
334
- const eventItem = screen.getByTestId('select-item-event-2');
335
- await user.click(eventItem);
336
-
337
- expect(setSelectedEventMock).toHaveBeenCalled();
338
- });
339
-
340
- it('displays selected event name and date', () => {
341
- renderWithProviders(<EventSelector />);
342
-
343
- expect(screen.getByTestId('select')).toHaveAttribute('data-value', 'event-1');
344
- });
345
- });
346
-
347
- describe('Event Sorting', () => {
348
- it('sorts events by date descending (newest first)', () => {
349
- const unsortedEvents: Event[] = [
350
- {
351
- id: 'event-old',
352
- event_id: 'event-old',
353
- event_name: 'Old Event',
354
- event_date: '2024-01-01T00:00:00Z',
355
- organisation_id: 'org-1',
356
- created_at: '2023-01-01T00:00:00Z',
357
- updated_at: '2023-01-01T00:00:00Z',
358
- },
359
- {
360
- id: 'event-new',
361
- event_id: 'event-new',
362
- event_name: 'New Event',
363
- event_date: '2024-12-31T00:00:00Z',
364
- organisation_id: 'org-1',
365
- created_at: '2023-01-02T00:00:00Z',
366
- updated_at: '2023-01-02T00:00:00Z',
367
- },
368
- ];
369
-
370
- mockUseEvents.mockReturnValue({
371
- ...defaultMockContext,
372
- events: unsortedEvents,
373
- });
374
-
375
- renderWithProviders(<EventSelector />);
376
-
377
- // The newest event should appear first in the DOM
378
- const items = screen.getAllByTestId(/^select-item-/);
379
- expect(items[0]).toHaveAttribute('data-testid', 'select-item-event-new');
380
- });
381
- });
382
-
383
- describe('Auto-Selection', () => {
384
- it('auto-selects next upcoming event when no event is selected', async () => {
385
- const setSelectedEventMock = vi.fn();
386
- const onEventChangeMock = vi.fn();
387
-
388
- // The component's useEffect depends on events.length, so when events.length changes,
389
- // it should trigger. However, refs are initialized with the current value, so we need
390
- // to simulate the scenario where events are loaded after the component mounts.
391
- //
392
- // Strategy: Start with a different events array length, then change it
393
- // This ensures eventsLengthChanged will be true
394
-
395
- // First render: Start with one event (different length than what we'll add)
396
- mockUseEvents.mockReturnValue({
397
- ...defaultMockContext,
398
- events: [mockEvents[0]], // Start with 1 event
399
- selectedEvent: null,
400
- isLoading: false,
401
- setSelectedEvent: setSelectedEventMock,
402
- refreshEvents: vi.fn(),
403
- clearEventSelection: vi.fn(),
404
- eventLoading: false,
405
- });
406
-
407
- const { rerender } = renderWithProviders(<EventSelector onEventChange={onEventChangeMock} />);
408
-
409
- // Wait for initial render
410
- await act(async () => {
411
- await new Promise(resolve => setTimeout(resolve, 10));
412
- });
413
-
414
- // Now change to all events (length changes from 1 to 3) - this triggers eventsLengthChanged
415
- await act(async () => {
416
- mockUseEvents.mockReturnValue({
417
- ...defaultMockContext,
418
- events: mockEvents, // Length changes from 1 to 3
419
- selectedEvent: null, // Still no event selected
420
- isLoading: false,
421
- setSelectedEvent: setSelectedEventMock,
422
- refreshEvents: vi.fn(),
423
- clearEventSelection: vi.fn(),
424
- eventLoading: false,
425
- });
426
-
427
- rerender(<EventSelector onEventChange={onEventChangeMock} />);
428
- });
429
-
430
- // Wait for the effect to run and auto-select
431
- await waitFor(() => {
432
- expect(setSelectedEventMock).toHaveBeenCalled();
433
- }, { timeout: 2000 });
434
-
435
- // Verify it selected an event
436
- // Note: The component selects the next upcoming event based on the current date
437
- // Since we're using real dates, it will select the event closest to today that's in the future
438
- // or the most recent past event if all are in the past
439
- expect(setSelectedEventMock).toHaveBeenCalled();
440
-
441
- // Get the actual event that was selected
442
- const selectedEvent = setSelectedEventMock.mock.calls[0][0];
443
- expect(selectedEvent).toBeDefined();
444
- expect(selectedEvent.event_id).toBeDefined();
445
- expect(selectedEvent.event_date).toBeDefined();
446
- });
447
- });
448
-
449
- describe('Event Details Display', () => {
450
- it('shows event details when showEventDetails is true', () => {
451
- renderWithProviders(<EventSelector showEventDetails={true} />);
452
-
453
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
454
- });
455
-
456
- it('hides event details when showEventDetails is false', () => {
457
- renderWithProviders(<EventSelector showEventDetails={false} />);
458
-
459
- // Details should not be visible in the rendered content
460
- const content = screen.getByTestId('select-content');
461
- expect(content).toBeInTheDocument();
462
- });
463
- });
464
-
465
- describe('Next Event Indicator', () => {
466
- it('shows next event indicator when showNextEventIndicator is true', () => {
467
- const today = new Date('2024-03-10T12:00:00Z');
468
- vi.useFakeTimers({ now: today });
469
-
470
- renderWithProviders(<EventSelector showNextEventIndicator={true} />);
471
-
472
- // Should render the star icon for next events (events-1, 2, 3 are in the future)
473
- expect(screen.getAllByTestId('icon-star').length).toBeGreaterThan(0);
474
- });
475
-
476
- it('hides next event indicator when showNextEventIndicator is false', () => {
477
- renderWithProviders(<EventSelector showNextEventIndicator={false} />);
478
-
479
- // No star icons should be present
480
- expect(screen.queryByTestId('icon-star')).not.toBeInTheDocument();
481
- });
482
- });
483
-
484
- describe('Format Event Date', () => {
485
- it('formats today\'s date as "Today"', () => {
486
- const today = new Date('2024-03-15T12:00:00Z');
487
- vi.useFakeTimers({ now: today });
488
-
489
- const todayEvent: Event[] = [{
490
- id: 'event-today',
491
- event_id: 'event-today',
492
- event_name: 'Today Event',
493
- event_date: '2024-03-15T00:00:00Z',
494
- organisation_id: 'org-1',
495
- created_at: '2024-01-01T00:00:00Z',
496
- updated_at: '2024-01-01T00:00:00Z',
497
- }];
498
-
499
- mockUseEvents.mockReturnValue({
500
- ...defaultMockContext,
501
- events: todayEvent,
502
- selectedEvent: todayEvent[0],
503
- });
504
-
505
- renderWithProviders(<EventSelector />);
506
-
507
- // Should display "Today" for today's event
508
- expect(screen.getByText('Today')).toBeInTheDocument();
509
- });
510
-
511
- it('formats tomorrow\'s date as "Tomorrow"', () => {
512
- const today = new Date('2024-03-15T12:00:00Z');
513
- vi.useFakeTimers({ now: today });
514
-
515
- const tomorrowEvent: Event[] = [{
516
- id: 'event-tomorrow',
517
- event_id: 'event-tomorrow',
518
- event_name: 'Tomorrow Event',
519
- event_date: '2024-03-16T00:00:00Z',
520
- organisation_id: 'org-1',
521
- created_at: '2024-01-01T00:00:00Z',
522
- updated_at: '2024-01-01T00:00:00Z',
523
- }];
524
-
525
- mockUseEvents.mockReturnValue({
526
- ...defaultMockContext,
527
- events: tomorrowEvent,
528
- selectedEvent: tomorrowEvent[0],
529
- });
530
-
531
- renderWithProviders(<EventSelector />);
532
-
533
- // Should display "Tomorrow" for tomorrow's event
534
- expect(screen.getByText('Tomorrow')).toBeInTheDocument();
535
- });
536
-
537
- it('formats past/future dates as locale date string', () => {
538
- const today = new Date('2024-03-15T12:00:00Z');
539
- vi.useFakeTimers({ now: today });
540
-
541
- const futureEvent: Event[] = [{
542
- id: 'event-future',
543
- event_id: 'event-future',
544
- event_name: 'Future Event',
545
- event_date: '2024-06-20T00:00:00Z',
546
- organisation_id: 'org-1',
547
- created_at: '2024-01-01T00:00:00Z',
548
- updated_at: '2024-01-01T00:00:00Z',
549
- }];
550
-
551
- mockUseEvents.mockReturnValue({
552
- ...defaultMockContext,
553
- events: futureEvent,
554
- selectedEvent: futureEvent[0],
555
- });
556
-
557
- renderWithProviders(<EventSelector />);
558
-
559
- // Should display formatted date
560
- const dateString = new Date('2024-06-20T00:00:00Z').toLocaleDateString();
561
- expect(screen.getByText(dateString)).toBeInTheDocument();
562
- });
563
- });
564
-
565
- describe('Is Next Event', () => {
566
- it('identifies events on or after today as next events', () => {
567
- const today = new Date('2024-03-15T12:00:00Z');
568
- vi.useFakeTimers({ now: today });
569
-
570
- const futureEvent: Event[] = [{
571
- id: 'event-future',
572
- event_id: 'event-future',
573
- event_name: 'Future Event',
574
- event_date: '2024-06-20T00:00:00Z',
575
- organisation_id: 'org-1',
576
- created_at: '2024-01-01T00:00:00Z',
577
- updated_at: '2024-01-01T00:00:00Z',
578
- }];
579
-
580
- mockUseEvents.mockReturnValue({
581
- ...defaultMockContext,
582
- events: futureEvent,
583
- });
584
-
585
- renderWithProviders(<EventSelector showNextEventIndicator={true} />);
586
-
587
- // Should show star indicator for future events
588
- expect(screen.getByTestId('icon-star')).toBeInTheDocument();
589
- });
590
-
591
- it('does not identify past events as next events', () => {
592
- const today = new Date('2024-03-15T12:00:00Z');
593
- vi.useFakeTimers({ now: today });
594
-
595
- const pastEvent: Event[] = [{
596
- id: 'event-past',
597
- event_id: 'event-past',
598
- event_name: 'Past Event',
599
- event_date: '2024-01-01T00:00:00Z',
600
- organisation_id: 'org-1',
601
- created_at: '2024-01-01T00:00:00Z',
602
- updated_at: '2024-01-01T00:00:00Z',
603
- }];
604
-
605
- mockUseEvents.mockReturnValue({
606
- ...defaultMockContext,
607
- events: pastEvent,
608
- });
609
-
610
- renderWithProviders(<EventSelector showNextEventIndicator={true} />);
611
-
612
- // Should not show star indicator for past events
613
- expect(screen.queryByTestId('icon-star')).not.toBeInTheDocument();
614
- });
615
- });
616
-
617
- describe('Integration with useEvents Hook', () => {
618
- it('uses events from useEvents hook', () => {
619
- const customEvents: Event[] = [
620
- {
621
- id: 'custom-event',
622
- event_id: 'custom-event',
623
- event_name: 'Custom Event',
624
- event_date: '2024-12-01T00:00:00Z',
625
- organisation_id: 'org-1',
626
- created_at: '2024-01-01T00:00:00Z',
627
- updated_at: '2024-01-01T00:00:00Z',
628
- },
629
- ];
630
-
631
- mockUseEvents.mockReturnValue({
632
- ...defaultMockContext,
633
- events: customEvents,
634
- });
635
-
636
- renderWithProviders(<EventSelector />);
637
-
638
- expect(mockUseEvents).toHaveBeenCalled();
639
- });
640
-
641
- it('calls setSelectedEvent from useEvents hook when event changes', async () => {
642
- const setSelectedEventMock = vi.fn();
643
-
644
- mockUseEvents.mockReturnValue({
645
- ...defaultMockContext,
646
- setSelectedEvent: setSelectedEventMock,
647
- });
648
-
649
- renderWithProviders(<EventSelector />);
650
-
651
- const user = userEvent.setup();
652
- const eventItem = screen.getByTestId('select-item-event-2');
653
- await user.click(eventItem);
654
-
655
- expect(setSelectedEventMock).toHaveBeenCalled();
656
- });
657
- });
658
-
659
- describe('Edge Cases', () => {
660
- it('handles events without event_date gracefully', () => {
661
- const eventsWithoutDate: Event[] = [{
662
- id: 'no-date-event',
663
- event_id: 'no-date-event',
664
- event_name: 'Event Without Date',
665
- organisation_id: 'org-1',
666
- created_at: '2024-01-01T00:00:00Z',
667
- updated_at: '2024-01-01T00:00:00Z',
668
- }];
669
-
670
- mockUseEvents.mockReturnValue({
671
- ...defaultMockContext,
672
- events: eventsWithoutDate,
673
- selectedEvent: eventsWithoutDate[0],
674
- });
675
-
676
- renderWithProviders(<EventSelector />);
677
-
678
- expect(screen.getByTestId('select')).toBeInTheDocument();
679
- });
680
-
681
- it('handles events with missing venue gracefully', () => {
682
- const eventsWithoutVenue: Event[] = [{
683
- id: 'no-venue-event',
684
- event_id: 'no-venue-event',
685
- event_name: 'Event Without Venue',
686
- event_date: '2024-12-01T00:00:00Z',
687
- organisation_id: 'org-1',
688
- created_at: '2024-01-01T00:00:00Z',
689
- updated_at: '2024-01-01T00:00:00Z',
690
- }];
691
-
692
- mockUseEvents.mockReturnValue({
693
- ...defaultMockContext,
694
- events: eventsWithoutVenue,
695
- selectedEvent: eventsWithoutVenue[0],
696
- });
697
-
698
- renderWithProviders(<EventSelector />);
699
-
700
- expect(screen.getByTestId('select')).toBeInTheDocument();
701
- });
702
-
703
- it('handles rapid event changes without errors', async () => {
704
- const user = userEvent.setup();
705
-
706
- renderWithProviders(<EventSelector />);
707
-
708
- // Rapidly change events
709
- const event1 = screen.getByTestId('select-item-event-1');
710
- const event2 = screen.getByTestId('select-item-event-2');
711
-
712
- await user.click(event1);
713
- await user.click(event2);
714
- await user.click(event1);
715
-
716
- expect(screen.getByTestId('select')).toBeInTheDocument();
717
- });
718
- });
719
- });
720
-