@jmruthers/pace-core 0.6.2 → 0.6.4

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-E7YQZD7D.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-QPXO24B4.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/chunk-36LVWXB2.js +227 -0
  11. package/dist/chunk-36LVWXB2.js.map +1 -0
  12. package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
  13. package/dist/chunk-3LPHPB62.js.map +1 -0
  14. package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
  15. package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
  16. package/dist/chunk-7JPAB3T5.js.map +1 -0
  17. package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
  18. package/dist/chunk-ATKZM7RX.js.map +1 -0
  19. package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
  20. package/dist/chunk-AVMLPIM7.js.map +1 -0
  21. package/dist/chunk-DGUM43GV.js +11 -0
  22. package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
  23. package/dist/chunk-I6DAQMWX.js.map +1 -0
  24. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  25. package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
  26. package/dist/chunk-NN6WWZ5U.js.map +1 -0
  27. package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
  28. package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
  29. package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
  30. package/dist/chunk-YKRAFF5K.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-OOPCLPZW.js +9 -0
  34. package/dist/contextValidator-OOPCLPZW.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 +10 -5
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +568 -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 +157 -36
  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 +285 -56
  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-E7YQZD7D.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
  298. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -1,784 +0,0 @@
1
- /**
2
- * @file OrganisationSelector Component Tests
3
- * @package @jmruthers/pace-core
4
- * @module Components/OrganisationSelector
5
- * @since 0.4.0
6
- *
7
- * Comprehensive test suite for the OrganisationSelector component covering all functionality,
8
- * RBAC behavior, error handling, and accessibility features.
9
- */
10
-
11
- import React from 'react';
12
- import { screen, waitFor } from '@testing-library/react';
13
- import userEvent from '@testing-library/user-event';
14
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
- import { OrganisationSelector, OrganisationSelectorProps } from './OrganisationSelector';
16
- import { renderWithProviders } from '../../__tests__/helpers/test-utils';
17
- import type { Organisation } from '../../types/organisation';
18
- import type { SelectProps, SelectContentProps, SelectItemProps, SelectTriggerProps, SelectValueProps } from '../Select';
19
- import type { ButtonProps } from '../Button/Button';
20
- import type { LoadingSpinnerProps } from '../LoadingSpinner/LoadingSpinner';
21
-
22
- // Mock the useOrganisationService hook (used internally by useOrganisations)
23
- const mockUseOrganisationService = vi.fn();
24
- vi.mock('../../hooks/services/useOrganisationService', () => ({
25
- useOrganisationService: () => mockUseOrganisationService(),
26
- }));
27
-
28
- // Mock the useOrganisations hook
29
- const mockUseOrganisations = vi.fn();
30
- vi.mock('../../hooks/useOrganisations', () => ({
31
- useOrganisations: () => mockUseOrganisations(),
32
- }));
33
-
34
- // Mock child components
35
- let mockOnValueChange: ((value: string) => void) | null = null;
36
-
37
- vi.mock('../Select', () => ({
38
- Select: ({ children, value, onValueChange, disabled }: SelectProps & { value?: string; onValueChange?: (value: string) => void; disabled?: boolean }) => {
39
- // Store the onValueChange callback for SelectItem to use
40
- mockOnValueChange = onValueChange || null;
41
- return (
42
- <div data-testid="select" data-value={value} data-disabled={disabled}>
43
- {children}
44
- </div>
45
- );
46
- },
47
- SelectContent: ({ children }: SelectContentProps) => (
48
- <div data-testid="select-content">{children}</div>
49
- ),
50
- SelectItem: ({ children, value, disabled, className }: SelectItemProps) => (
51
- <div
52
- data-testid={`select-item-${value}`}
53
- data-disabled={disabled}
54
- className={className}
55
- onClick={() => !disabled && mockOnValueChange?.(value)}
56
- >
57
- {children}
58
- </div>
59
- ),
60
- SelectTrigger: ({ children, className }: SelectTriggerProps) => (
61
- <button data-testid="select-trigger" className={className}>
62
- {children}
63
- </button>
64
- ),
65
- SelectValue: ({ placeholder }: SelectValueProps) => (
66
- <span data-testid="select-value">{placeholder}</span>
67
- ),
68
- }));
69
-
70
- vi.mock('../Alert/Alert', () => ({
71
- Alert: ({ children, variant }: React.HTMLAttributes<HTMLDivElement> & { variant?: "default" | "destructive" | "inline" }) => (
72
- <div data-testid="alert" data-variant={variant}>
73
- {children}
74
- </div>
75
- ),
76
- AlertDescription: ({ children }: React.HTMLAttributes<HTMLDivElement>) => (
77
- <div data-testid="alert-description">{children}</div>
78
- ),
79
- }));
80
-
81
- vi.mock('../Button/Button', () => ({
82
- Button: ({ children, onClick, disabled, variant, size, className }: ButtonProps) => (
83
- <button
84
- data-testid="button"
85
- onClick={onClick}
86
- disabled={disabled}
87
- data-variant={variant}
88
- data-size={size}
89
- className={className}
90
- >
91
- {children}
92
- </button>
93
- ),
94
- }));
95
-
96
- vi.mock('../LoadingSpinner/LoadingSpinner', () => ({
97
- LoadingSpinner: ({ size }: LoadingSpinnerProps) => (
98
- <div data-testid="loading-spinner" data-size={size}>Loading...</div>
99
- ),
100
- }));
101
-
102
- // Test data
103
- const mockOrganisations: Organisation[] = [
104
- {
105
- id: 'org-1',
106
- name: 'acme-corp',
107
- display_name: 'Acme Corporation',
108
- description: 'Leading technology company',
109
- subscription_tier: 'premium',
110
- settings: {},
111
- is_active: true,
112
- created_at: '2023-01-01T00:00:00Z',
113
- updated_at: '2023-01-01T00:00:00Z',
114
- },
115
- {
116
- id: 'org-2',
117
- name: 'beta-inc',
118
- display_name: 'Beta Inc',
119
- description: 'Innovation focused startup',
120
- subscription_tier: 'standard',
121
- settings: {},
122
- is_active: true,
123
- created_at: '2023-01-02T00:00:00Z',
124
- updated_at: '2023-01-02T00:00:00Z',
125
- },
126
- {
127
- id: 'org-3',
128
- name: 'gamma-ltd',
129
- display_name: 'Gamma Ltd',
130
- description: 'Global enterprise solutions',
131
- subscription_tier: 'enterprise',
132
- settings: {},
133
- is_active: true,
134
- created_at: '2023-01-03T00:00:00Z',
135
- updated_at: '2023-01-03T00:00:00Z',
136
- },
137
- ];
138
-
139
- const mockSelectedOrganisation = mockOrganisations[0];
140
-
141
- const defaultMockContext = {
142
- organisations: mockOrganisations,
143
- selectedOrganisation: mockSelectedOrganisation,
144
- isLoading: false,
145
- error: null,
146
- switchOrganisation: vi.fn().mockResolvedValue(undefined),
147
- getUserRole: vi.fn().mockReturnValue('admin'),
148
- validateOrganisationAccess: vi.fn().mockReturnValue(true),
149
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
150
- };
151
-
152
- describe('OrganisationSelector Component', () => {
153
- beforeEach(() => {
154
- vi.clearAllMocks();
155
-
156
- // Re-setup the default mock context after clearing mocks
157
- const mockContext = {
158
- organisations: mockOrganisations,
159
- selectedOrganisation: mockSelectedOrganisation,
160
- isLoading: false,
161
- error: null,
162
- switchOrganisation: vi.fn().mockResolvedValue(undefined),
163
- getUserRole: vi.fn().mockReturnValue('admin'),
164
- validateOrganisationAccess: vi.fn().mockReturnValue(true), // Ensure all orgs are accessible by default
165
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
166
- };
167
-
168
- mockUseOrganisations.mockReturnValue(mockContext);
169
- });
170
-
171
- afterEach(() => {
172
- vi.clearAllMocks();
173
- });
174
-
175
- // Basic rendering tests
176
- describe('Rendering', () => {
177
- it('renders with default props', () => {
178
- renderWithProviders(<OrganisationSelector />);
179
-
180
- expect(screen.getByTestId('select')).toBeInTheDocument();
181
- expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
182
- expect(screen.getByTestId('select-value')).toHaveTextContent('Select organisation');
183
- });
184
-
185
- it('renders with custom placeholder', () => {
186
- renderWithProviders(<OrganisationSelector placeholder="Choose organisation..." />);
187
-
188
- expect(screen.getByTestId('select-value')).toHaveTextContent('Choose organisation...');
189
- });
190
-
191
- it('renders with custom className', () => {
192
- renderWithProviders(<OrganisationSelector className="custom-selector" />);
193
-
194
- const container = screen.getByTestId('select').parentElement;
195
- expect(container).toHaveClass('custom-selector');
196
- });
197
-
198
- it('renders with proper DOM structure', () => {
199
- renderWithProviders(<OrganisationSelector />);
200
-
201
- expect(screen.getByTestId('select')).toBeInTheDocument();
202
- expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
203
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
204
- });
205
- });
206
-
207
- // Loading state tests
208
- describe('Loading State', () => {
209
- it('renders loading state when organisations are loading', () => {
210
- mockUseOrganisations.mockReturnValue({
211
- ...defaultMockContext,
212
- isLoading: true,
213
- });
214
-
215
- renderWithProviders(<OrganisationSelector />);
216
-
217
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
218
- expect(screen.getByText('Loading organisations...')).toBeInTheDocument();
219
- });
220
-
221
- it('renders compact loading state', () => {
222
- mockUseOrganisations.mockReturnValue({
223
- ...defaultMockContext,
224
- isLoading: true,
225
- });
226
-
227
- renderWithProviders(<OrganisationSelector compact={true} />);
228
-
229
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
230
- expect(screen.getAllByText('Loading...')).toHaveLength(2); // Spinner + text
231
- });
232
-
233
- it('shows loading spinner in trigger when switching organisations', () => {
234
- // Test the loading state directly by mocking the loading state
235
- mockUseOrganisations.mockReturnValue({
236
- ...defaultMockContext,
237
- isLoading: true,
238
- });
239
-
240
- renderWithProviders(<OrganisationSelector />);
241
-
242
- // Should show loading state
243
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
244
- });
245
- });
246
-
247
- // Error state tests
248
- describe('Error State', () => {
249
- it('renders error state when organisations fail to load', () => {
250
- const error = new Error('Failed to load organisations');
251
- mockUseOrganisations.mockReturnValue({
252
- ...defaultMockContext,
253
- error,
254
- organisations: [],
255
- });
256
-
257
- renderWithProviders(<OrganisationSelector />);
258
-
259
- expect(screen.getByTestId('alert')).toBeInTheDocument();
260
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Failed to load organisations');
261
- });
262
-
263
- it('shows retry button when showRetryButton is true', () => {
264
- const error = new Error('Failed to load organisations');
265
- mockUseOrganisations.mockReturnValue({
266
- ...defaultMockContext,
267
- error,
268
- organisations: [],
269
- });
270
-
271
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
272
-
273
- expect(screen.getByTestId('button')).toBeInTheDocument();
274
- expect(screen.getByTestId('button')).toHaveTextContent('Retry');
275
- });
276
-
277
- it('does not show retry button when showRetryButton is false', () => {
278
- const error = new Error('Failed to load organisations');
279
- mockUseOrganisations.mockReturnValue({
280
- ...defaultMockContext,
281
- error,
282
- organisations: [],
283
- });
284
-
285
- renderWithProviders(<OrganisationSelector showRetryButton={false} />);
286
-
287
- expect(screen.queryByTestId('button')).not.toBeInTheDocument();
288
- });
289
-
290
- it('handles retry button click', async () => {
291
- const user = userEvent.setup();
292
- const refreshOrganisations = vi.fn().mockResolvedValue(undefined);
293
-
294
- const error = new Error('Failed to load organisations');
295
- mockUseOrganisations.mockReturnValue({
296
- ...defaultMockContext,
297
- error,
298
- organisations: [],
299
- refreshOrganisations,
300
- });
301
-
302
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
303
-
304
- const retryButton = screen.getByTestId('button');
305
- await user.click(retryButton);
306
-
307
- expect(refreshOrganisations).toHaveBeenCalledTimes(1);
308
- });
309
- });
310
-
311
- // Empty state tests
312
- describe('Empty State', () => {
313
- it('renders no organisations message when showNoOrganisationsMessage is true', () => {
314
- mockUseOrganisations.mockReturnValue({
315
- ...defaultMockContext,
316
- organisations: [],
317
- });
318
-
319
- renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={true} />);
320
-
321
- expect(screen.getByTestId('alert')).toBeInTheDocument();
322
- expect(screen.getByTestId('alert-description')).toHaveTextContent('No organisations available. Please contact your administrator to be added to an organisation.');
323
- });
324
-
325
- it('shows check again button when no organisations and showRetryButton is true', () => {
326
- mockUseOrganisations.mockReturnValue({
327
- ...defaultMockContext,
328
- organisations: [],
329
- });
330
-
331
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
332
-
333
- expect(screen.getByTestId('button')).toBeInTheDocument();
334
- expect(screen.getByTestId('button')).toHaveTextContent('Check Again');
335
- });
336
-
337
- it('returns null when no organisations and showNoOrganisationsMessage is false', () => {
338
- mockUseOrganisations.mockReturnValue({
339
- ...defaultMockContext,
340
- organisations: [],
341
- });
342
-
343
- const { container } = renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={false} />);
344
-
345
- expect(container.firstChild).toBeNull();
346
- });
347
- });
348
-
349
- // Organisation switching tests
350
- describe('Organisation Switching', () => {
351
- it('renders all available organisations', () => {
352
- renderWithProviders(<OrganisationSelector />);
353
-
354
- mockOrganisations.forEach(org => {
355
- expect(screen.getByTestId(`select-item-${org.id}`)).toBeInTheDocument();
356
- });
357
- });
358
-
359
- it('displays organisation names and descriptions', () => {
360
- renderWithProviders(<OrganisationSelector />);
361
-
362
- expect(screen.getByText('Acme Corporation')).toBeInTheDocument();
363
- expect(screen.getByText('Leading technology company')).toBeInTheDocument();
364
- expect(screen.getByText('Beta Inc')).toBeInTheDocument();
365
- expect(screen.getByText('Innovation focused startup')).toBeInTheDocument();
366
- });
367
-
368
- it('handles organisation selection', async () => {
369
- const user = userEvent.setup();
370
- const onOrganisationChange = vi.fn();
371
- const switchOrganisation = vi.fn().mockResolvedValue(undefined);
372
- const validateOrganisationAccess = vi.fn().mockReturnValue(true);
373
-
374
- mockUseOrganisations.mockReturnValue({
375
- organisations: mockOrganisations,
376
- selectedOrganisation: mockSelectedOrganisation,
377
- isLoading: false,
378
- error: null,
379
- switchOrganisation,
380
- getUserRole: vi.fn().mockReturnValue('admin'),
381
- validateOrganisationAccess,
382
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
383
- });
384
-
385
- renderWithProviders(
386
- <OrganisationSelector onOrganisationChange={onOrganisationChange} />
387
- );
388
-
389
- const selectItem = screen.getByTestId('select-item-org-2');
390
- await user.click(selectItem);
391
-
392
- expect(switchOrganisation).toHaveBeenCalledWith('org-2');
393
- });
394
-
395
- it('calls onOrganisationChange callback after successful switch', async () => {
396
- const user = userEvent.setup();
397
- const onOrganisationChange = vi.fn();
398
- const switchOrganisation = vi.fn().mockResolvedValue(undefined);
399
- const validateOrganisationAccess = vi.fn().mockReturnValue(true);
400
-
401
- mockUseOrganisations.mockReturnValue({
402
- organisations: mockOrganisations,
403
- selectedOrganisation: mockSelectedOrganisation,
404
- isLoading: false,
405
- error: null,
406
- switchOrganisation,
407
- getUserRole: vi.fn().mockReturnValue('admin'),
408
- validateOrganisationAccess,
409
- refreshOrganisations: vi.fn().mockResolvedValue(undefined),
410
- });
411
-
412
- renderWithProviders(
413
- <OrganisationSelector onOrganisationChange={onOrganisationChange} />
414
- );
415
-
416
- const selectItem = screen.getByTestId('select-item-org-2');
417
- await user.click(selectItem);
418
-
419
- await waitFor(() => {
420
- expect(onOrganisationChange).toHaveBeenCalledWith(mockOrganisations[1]);
421
- });
422
- });
423
-
424
- it('handles organisation switch errors', () => {
425
- // Test error state directly by mocking an error
426
- const error = new Error('Access denied');
427
- mockUseOrganisations.mockReturnValue({
428
- ...defaultMockContext,
429
- error,
430
- organisations: [],
431
- });
432
-
433
- renderWithProviders(<OrganisationSelector />);
434
-
435
- expect(screen.getByTestId('alert')).toBeInTheDocument();
436
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Access denied');
437
- });
438
-
439
- it('validates organisation access before switching', async () => {
440
- const user = userEvent.setup();
441
- const validateOrganisationAccess = vi.fn().mockReturnValue(false);
442
- const switchOrganisation = vi.fn();
443
-
444
- mockUseOrganisations.mockReturnValue({
445
- ...defaultMockContext,
446
- validateOrganisationAccess,
447
- switchOrganisation,
448
- });
449
-
450
- renderWithProviders(<OrganisationSelector />);
451
-
452
- const selectItem = screen.getByTestId('select-item-org-2');
453
- await user.click(selectItem);
454
-
455
- expect(validateOrganisationAccess).toHaveBeenCalledWith('org-2');
456
- expect(switchOrganisation).not.toHaveBeenCalled();
457
- });
458
- });
459
-
460
- // RBAC and permission tests
461
- describe('RBAC and Permissions', () => {
462
- it('disables organisations without access', () => {
463
- const validateOrganisationAccess = vi.fn((orgId: string) => orgId !== 'org-2');
464
-
465
- mockUseOrganisations.mockReturnValue({
466
- ...defaultMockContext,
467
- validateOrganisationAccess,
468
- });
469
-
470
- renderWithProviders(<OrganisationSelector />);
471
-
472
- expect(screen.getByTestId('select-item-org-1')).not.toHaveAttribute('data-disabled', 'true');
473
- expect(screen.getByTestId('select-item-org-2')).toHaveAttribute('data-disabled', 'true');
474
- expect(screen.getByTestId('select-item-org-3')).not.toHaveAttribute('data-disabled', 'true');
475
- });
476
-
477
- it('shows user role when showRole is true', () => {
478
- const getUserRole = vi.fn().mockReturnValue('admin');
479
-
480
- mockUseOrganisations.mockReturnValue({
481
- ...defaultMockContext,
482
- getUserRole,
483
- });
484
-
485
- renderWithProviders(<OrganisationSelector showRole={true} />);
486
-
487
- // The role should be displayed in the select items
488
- expect(screen.getAllByText('admin')).toHaveLength(3);
489
- });
490
-
491
- it('does not show role when showRole is false', () => {
492
- const getUserRole = vi.fn().mockReturnValue('admin');
493
-
494
- mockUseOrganisations.mockReturnValue({
495
- ...defaultMockContext,
496
- getUserRole,
497
- });
498
-
499
- renderWithProviders(<OrganisationSelector showRole={false} />);
500
-
501
- expect(screen.queryByText('admin')).not.toBeInTheDocument();
502
- });
503
-
504
- it('formats role names correctly', () => {
505
- const getUserRole = vi.fn().mockReturnValue('super_admin');
506
-
507
- mockUseOrganisations.mockReturnValue({
508
- ...defaultMockContext,
509
- getUserRole,
510
- });
511
-
512
- renderWithProviders(<OrganisationSelector showRole={true} />);
513
-
514
- expect(screen.getAllByText('super admin')).toHaveLength(3);
515
- });
516
- });
517
-
518
- // Compact mode tests
519
- describe('Compact Mode', () => {
520
- it('hides descriptions in compact mode', () => {
521
- renderWithProviders(<OrganisationSelector compact={true} />);
522
-
523
- expect(screen.queryByText('Leading technology company')).not.toBeInTheDocument();
524
- expect(screen.queryByText('Innovation focused startup')).not.toBeInTheDocument();
525
- });
526
-
527
- it('shows descriptions when not in compact mode', () => {
528
- renderWithProviders(<OrganisationSelector compact={false} />);
529
-
530
- expect(screen.getByText('Leading technology company')).toBeInTheDocument();
531
- expect(screen.getByText('Innovation focused startup')).toBeInTheDocument();
532
- });
533
- });
534
-
535
- // Disabled state tests
536
- describe('Disabled State', () => {
537
- it('disables the selector when disabled prop is true', () => {
538
- renderWithProviders(<OrganisationSelector disabled={true} />);
539
-
540
- expect(screen.getByTestId('select')).toHaveAttribute('data-disabled', 'true');
541
- });
542
-
543
- it('enables the selector when disabled prop is false', () => {
544
- renderWithProviders(<OrganisationSelector disabled={false} />);
545
-
546
- expect(screen.getByTestId('select')).toHaveAttribute('data-disabled', 'false');
547
- });
548
-
549
- it('disables selector during loading', () => {
550
- // Test the disabled state directly by mocking the loading state
551
- mockUseOrganisations.mockReturnValue({
552
- ...defaultMockContext,
553
- isLoading: true,
554
- });
555
-
556
- renderWithProviders(<OrganisationSelector />);
557
-
558
- // Should show loading state instead of selector
559
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
560
- });
561
- });
562
-
563
- // Accessibility tests
564
- describe('Accessibility', () => {
565
- it('has proper ARIA attributes', () => {
566
- renderWithProviders(<OrganisationSelector />);
567
-
568
- const select = screen.getByTestId('select');
569
- expect(select).toBeInTheDocument();
570
- });
571
-
572
- it('provides screen reader accessible content', () => {
573
- renderWithProviders(<OrganisationSelector />);
574
-
575
- expect(screen.getByText('Acme Corporation')).toBeInTheDocument();
576
- expect(screen.getByText('Beta Inc')).toBeInTheDocument();
577
- });
578
-
579
- it('shows loading state to screen readers', () => {
580
- mockUseOrganisations.mockReturnValue({
581
- ...defaultMockContext,
582
- isLoading: true,
583
- });
584
-
585
- renderWithProviders(<OrganisationSelector />);
586
-
587
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
588
- expect(screen.getByText('Loading organisations...')).toBeInTheDocument();
589
- });
590
-
591
- it('announces errors to screen readers', () => {
592
- const error = new Error('Failed to load organisations');
593
- mockUseOrganisations.mockReturnValue({
594
- ...defaultMockContext,
595
- error,
596
- organisations: [],
597
- });
598
-
599
- renderWithProviders(<OrganisationSelector />);
600
-
601
- expect(screen.getByTestId('alert')).toBeInTheDocument();
602
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Failed to load organisations');
603
- });
604
- });
605
-
606
- // Error handling tests
607
- describe('Error Handling', () => {
608
- it('handles switch errors gracefully', () => {
609
- // Test error state directly by mocking an error
610
- const error = new Error('Network error');
611
- mockUseOrganisations.mockReturnValue({
612
- ...defaultMockContext,
613
- error,
614
- organisations: [],
615
- });
616
-
617
- renderWithProviders(<OrganisationSelector />);
618
-
619
- expect(screen.getByTestId('alert')).toBeInTheDocument();
620
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Network error');
621
- });
622
-
623
- it('handles retry errors gracefully', () => {
624
- // Test error state directly by mocking an error
625
- const error = new Error('Retry failed');
626
- mockUseOrganisations.mockReturnValue({
627
- ...defaultMockContext,
628
- error,
629
- organisations: [],
630
- });
631
-
632
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
633
-
634
- expect(screen.getByTestId('alert')).toBeInTheDocument();
635
- expect(screen.getByTestId('alert-description')).toHaveTextContent('Failed to load organisations: Retry failed');
636
- });
637
-
638
- it('clears errors on successful retry', async () => {
639
- const user = userEvent.setup();
640
- const refreshOrganisations = vi.fn().mockResolvedValue(undefined);
641
-
642
- const error = new Error('Failed to load organisations');
643
- mockUseOrganisations.mockReturnValue({
644
- ...defaultMockContext,
645
- error,
646
- organisations: [],
647
- refreshOrganisations,
648
- });
649
-
650
- renderWithProviders(<OrganisationSelector showRetryButton={true} />);
651
-
652
- const retryButton = screen.getByTestId('button');
653
- await user.click(retryButton);
654
-
655
- await waitFor(() => {
656
- expect(refreshOrganisations).toHaveBeenCalledTimes(1);
657
- });
658
- });
659
- });
660
-
661
- // Edge cases and prop validation tests
662
- describe('Edge Cases and Prop Validation', () => {
663
- it('handles empty organisations array', () => {
664
- mockUseOrganisations.mockReturnValue({
665
- ...defaultMockContext,
666
- organisations: [],
667
- });
668
-
669
- renderWithProviders(<OrganisationSelector showNoOrganisationsMessage={true} />);
670
-
671
- expect(screen.getByTestId('alert')).toBeInTheDocument();
672
- expect(screen.getByTestId('alert-description')).toHaveTextContent('No organisations available');
673
- });
674
-
675
- it('handles undefined onOrganisationChange callback', async () => {
676
- const user = userEvent.setup();
677
-
678
- renderWithProviders(<OrganisationSelector />);
679
-
680
- const selectItem = screen.getByTestId('select-item-org-2');
681
- await user.click(selectItem);
682
-
683
- // Should not throw error
684
- expect(screen.getByTestId('select')).toBeInTheDocument();
685
- });
686
-
687
- it('handles null selectedOrganisation gracefully', () => {
688
- mockUseOrganisations.mockReturnValue({
689
- ...defaultMockContext,
690
- selectedOrganisation: null,
691
- isLoading: true, // This should trigger loading state
692
- });
693
-
694
- // This should render the loading state instead of crashing
695
- renderWithProviders(<OrganisationSelector />);
696
- expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
697
- });
698
-
699
- it('handles very long organisation names', () => {
700
- const longNameOrg = {
701
- ...mockOrganisations[0],
702
- display_name: 'A'.repeat(1000),
703
- };
704
-
705
- mockUseOrganisations.mockReturnValue({
706
- ...defaultMockContext,
707
- organisations: [longNameOrg],
708
- });
709
-
710
- renderWithProviders(<OrganisationSelector />);
711
-
712
- expect(screen.getByText(longNameOrg.display_name)).toBeInTheDocument();
713
- });
714
- });
715
-
716
- // Performance tests
717
- describe('Performance', () => {
718
- it('renders quickly with many organisations', () => {
719
- const manyOrgs = Array.from({ length: 100 }, (_, i) => ({
720
- ...mockOrganisations[0],
721
- id: `org-${i}`,
722
- display_name: `Organisation ${i}`,
723
- }));
724
-
725
- mockUseOrganisations.mockReturnValue({
726
- ...defaultMockContext,
727
- organisations: manyOrgs,
728
- });
729
-
730
- const startTime = performance.now();
731
- renderWithProviders(<OrganisationSelector />);
732
- const endTime = performance.now();
733
-
734
- expect(endTime - startTime).toBeLessThan(500); // Should render in under 500ms
735
- });
736
-
737
- it('handles rapid organisation switches', async () => {
738
- const user = userEvent.setup();
739
-
740
- renderWithProviders(<OrganisationSelector />);
741
-
742
- // Rapid switches
743
- for (let i = 0; i < 5; i++) {
744
- const selectItem = screen.getByTestId(`select-item-org-${(i % 3) + 1}`);
745
- await user.click(selectItem);
746
- }
747
-
748
- expect(screen.getByTestId('select')).toBeInTheDocument();
749
- });
750
- });
751
-
752
- // Integration tests
753
- describe('Integration Scenarios', () => {
754
- it('works with all props enabled', () => {
755
- renderWithProviders(
756
- <OrganisationSelector
757
- placeholder="Choose organisation..."
758
- className="custom-class"
759
- showRole={true}
760
- compact={false}
761
- showRetryButton={true}
762
- showNoOrganisationsMessage={true}
763
- disabled={false}
764
- />
765
- );
766
-
767
- expect(screen.getByTestId('select')).toBeInTheDocument();
768
- expect(screen.getByText('Choose organisation...')).toBeInTheDocument();
769
- });
770
-
771
- it('works in minimal configuration', () => {
772
- renderWithProviders(
773
- <OrganisationSelector
774
- showRole={false}
775
- compact={true}
776
- showRetryButton={false}
777
- showNoOrganisationsMessage={false}
778
- />
779
- );
780
-
781
- expect(screen.getByTestId('select')).toBeInTheDocument();
782
- });
783
- });
784
- });