@jmruthers/pace-core 0.5.74 → 0.5.75

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 (278) hide show
  1. package/dist/{DataTable-2QR5TER5.js → DataTable-HWZQGASI.js} +8 -8
  2. package/dist/{PublicLoadingSpinner-DLpF5bbs.d.ts → PublicLoadingSpinner-BKNBT6b6.d.ts} +2 -2
  3. package/dist/RBACService-C4udt_Zp.d.ts +528 -0
  4. package/dist/{UnifiedAuthProvider-K4NRGXL4.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
  5. package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
  6. package/dist/{chunk-UJMCGBLS.js → chunk-2CHATWBF.js} +5 -7
  7. package/dist/chunk-2CHATWBF.js.map +1 -0
  8. package/dist/{chunk-BKVGJVUR.js → chunk-2DFZ432F.js} +496 -30
  9. package/dist/chunk-2DFZ432F.js.map +1 -0
  10. package/dist/{chunk-LVQ26TCN.js → chunk-33PHABLB.js} +36 -3
  11. package/dist/chunk-33PHABLB.js.map +1 -0
  12. package/dist/chunk-5F3NDPJV.js +232 -0
  13. package/dist/chunk-5F3NDPJV.js.map +1 -0
  14. package/dist/chunk-A4FUBC7B.js +17 -0
  15. package/dist/chunk-A4FUBC7B.js.map +1 -0
  16. package/dist/{chunk-SMJZMKYN.js → chunk-A6HBIY5P.js} +2 -11
  17. package/dist/{chunk-SMJZMKYN.js.map → chunk-A6HBIY5P.js.map} +1 -1
  18. package/dist/{chunk-IHMMNKNA.js → chunk-CY3AHGO4.js} +6256 -1937
  19. package/dist/chunk-CY3AHGO4.js.map +1 -0
  20. package/dist/{chunk-H2TNUICK.js → chunk-DAXLNIDY.js} +47 -49
  21. package/dist/chunk-DAXLNIDY.js.map +1 -0
  22. package/dist/{chunk-VKOCWWVY.js → chunk-L3RV2ALE.js} +1 -6
  23. package/dist/{chunk-VKOCWWVY.js.map → chunk-L3RV2ALE.js.map} +1 -1
  24. package/dist/chunk-LW7MMEAQ.js +59 -0
  25. package/dist/chunk-LW7MMEAQ.js.map +1 -0
  26. package/dist/{chunk-DG5Z55HH.js → chunk-NTNILOBC.js} +7 -9
  27. package/dist/chunk-NTNILOBC.js.map +1 -0
  28. package/dist/chunk-PYUXFQJ3.js +11 -0
  29. package/dist/chunk-PYUXFQJ3.js.map +1 -0
  30. package/dist/chunk-URUTVZ7N.js +27 -0
  31. package/dist/chunk-URUTVZ7N.js.map +1 -0
  32. package/dist/chunk-WN6XJWOS.js +2468 -0
  33. package/dist/chunk-WN6XJWOS.js.map +1 -0
  34. package/dist/{chunk-3SP4P7NS.js → chunk-XLZ7U46Z.js} +59 -1
  35. package/dist/chunk-XLZ7U46Z.js.map +1 -0
  36. package/dist/{chunk-ORSMVXO2.js → chunk-ZTT2AXMX.js} +9 -14
  37. package/dist/chunk-ZTT2AXMX.js.map +1 -0
  38. package/dist/components.d.ts +4 -5
  39. package/dist/components.js +32 -39
  40. package/dist/components.js.map +1 -1
  41. package/dist/hooks.d.ts +3 -3
  42. package/dist/hooks.js +9 -8
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +156 -10
  45. package/dist/index.js +188 -93
  46. package/dist/index.js.map +1 -1
  47. package/dist/{organisation-t-vvQC3g.d.ts → organisation-BtshODVF.d.ts} +4 -3
  48. package/dist/providers.d.ts +27 -38
  49. package/dist/providers.js +33 -23
  50. package/dist/rbac/index.d.ts +61 -5
  51. package/dist/rbac/index.js +13 -14
  52. package/dist/styles/index.js +2 -2
  53. package/dist/theming/runtime.js +1 -3
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CMPjE_fv.d.ts → unified-CM7T0aTK.d.ts} +1 -1
  58. package/dist/useInactivityTracker-MRUU55XI.js +10 -0
  59. package/dist/useInactivityTracker-MRUU55XI.js.map +1 -0
  60. package/dist/{usePublicRouteParams-Ua1Vz-HG.d.ts → usePublicRouteParams-B-CumWRc.d.ts} +3 -3
  61. package/dist/utils.js +7 -9
  62. package/dist/utils.js.map +1 -1
  63. package/dist/validation.d.ts +1 -1
  64. package/docs/api/classes/ColumnFactory.md +1 -1
  65. package/docs/api/classes/ErrorBoundary.md +1 -1
  66. package/docs/api/classes/InvalidScopeError.md +1 -1
  67. package/docs/api/classes/MissingUserContextError.md +1 -1
  68. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  69. package/docs/api/classes/PermissionDeniedError.md +1 -1
  70. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  71. package/docs/api/classes/RBACAuditManager.md +1 -1
  72. package/docs/api/classes/RBACCache.md +1 -1
  73. package/docs/api/classes/RBACEngine.md +1 -1
  74. package/docs/api/classes/RBACError.md +1 -1
  75. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  76. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  77. package/docs/api/classes/StorageUtils.md +1 -1
  78. package/docs/api/enums/FileCategory.md +1 -1
  79. package/docs/api/interfaces/AggregateConfig.md +1 -1
  80. package/docs/api/interfaces/ButtonProps.md +3 -3
  81. package/docs/api/interfaces/CardProps.md +2 -2
  82. package/docs/api/interfaces/ColorPalette.md +1 -1
  83. package/docs/api/interfaces/ColorShade.md +1 -1
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataTableAction.md +1 -1
  86. package/docs/api/interfaces/DataTableColumn.md +1 -1
  87. package/docs/api/interfaces/DataTableProps.md +1 -1
  88. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  89. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  90. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/EventLogoProps.md +2 -2
  92. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  93. package/docs/api/interfaces/FileMetadata.md +1 -1
  94. package/docs/api/interfaces/FileReference.md +1 -1
  95. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  96. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  97. package/docs/api/interfaces/FileUploadProps.md +1 -1
  98. package/docs/api/interfaces/FooterProps.md +1 -1
  99. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  100. package/docs/api/interfaces/InputProps.md +2 -2
  101. package/docs/api/interfaces/LabelProps.md +1 -1
  102. package/docs/api/interfaces/LoginFormProps.md +1 -1
  103. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  104. package/docs/api/interfaces/NavigationContextType.md +1 -1
  105. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  106. package/docs/api/interfaces/NavigationItem.md +1 -1
  107. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  108. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  109. package/docs/api/interfaces/Organisation.md +1 -1
  110. package/docs/api/interfaces/OrganisationContextType.md +28 -17
  111. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  112. package/docs/api/interfaces/OrganisationProviderProps.md +2 -2
  113. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  114. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  115. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  116. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  117. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  118. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  119. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  120. package/docs/api/interfaces/PaletteData.md +1 -1
  121. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  122. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  123. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  124. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +2 -2
  125. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  126. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  127. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  128. package/docs/api/interfaces/RBACConfig.md +1 -1
  129. package/docs/api/interfaces/RBACContextType.md +5 -11
  130. package/docs/api/interfaces/RBACLogger.md +1 -1
  131. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  132. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  134. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  135. package/docs/api/interfaces/RouteConfig.md +1 -1
  136. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  137. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  138. package/docs/api/interfaces/StorageConfig.md +1 -1
  139. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  140. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  141. package/docs/api/interfaces/StorageListOptions.md +1 -1
  142. package/docs/api/interfaces/StorageListResult.md +1 -1
  143. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  144. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  145. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  146. package/docs/api/interfaces/StyleImport.md +1 -1
  147. package/docs/api/interfaces/SwitchProps.md +1 -1
  148. package/docs/api/interfaces/ToastActionElement.md +1 -1
  149. package/docs/api/interfaces/ToastProps.md +1 -1
  150. package/docs/api/interfaces/UnifiedAuthContextType.md +524 -440
  151. package/docs/api/interfaces/UnifiedAuthProviderProps.md +14 -14
  152. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  153. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  154. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  158. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  159. package/docs/api/interfaces/UserEventAccess.md +11 -11
  160. package/docs/api/interfaces/UserMenuProps.md +1 -1
  161. package/docs/api/interfaces/UserProfile.md +1 -1
  162. package/docs/api/modules.md +179 -52
  163. package/docs/architecture/services.md +30 -32
  164. package/docs/breaking-changes.md +2 -5
  165. package/docs/migration/service-architecture.md +121 -260
  166. package/docs/rbac/README-rbac-rls-integration.md +48 -38
  167. package/{src/rbac/examples → examples/RBAC}/CompleteRBACExample.tsx +3 -2
  168. package/{src/rbac/examples → examples/RBAC}/EventBasedApp.tsx +5 -4
  169. package/{src/components/examples → examples/RBAC}/PermissionExample.tsx +7 -6
  170. package/examples/RBAC/__tests__/PermissionExample.test.tsx +150 -0
  171. package/examples/RBAC/index.ts +13 -0
  172. package/examples/README.md +37 -0
  173. package/examples/index.ts +22 -0
  174. package/{src/examples → examples/public-pages}/CorrectPublicPageImplementation.tsx +1 -1
  175. package/{src/examples → examples/public-pages}/PublicEventPage.tsx +1 -1
  176. package/{src/examples → examples/public-pages}/PublicPageApp.tsx +1 -1
  177. package/{src/examples → examples/public-pages}/PublicPageUsageExample.tsx +1 -1
  178. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +159 -0
  179. package/examples/public-pages/index.ts +14 -0
  180. package/package.json +22 -18
  181. package/src/__tests__/TEST_GUIDE_CURSOR.md +650 -9
  182. package/src/__tests__/helpers/README.md +255 -0
  183. package/src/__tests__/helpers/index.ts +62 -0
  184. package/src/__tests__/helpers/supabaseMock.ts +27 -3
  185. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -8
  186. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +55 -0
  187. package/src/components/DataTable/core/ColumnManager.ts +10 -0
  188. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +254 -0
  189. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +193 -0
  190. package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +45 -0
  191. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +117 -0
  192. package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +71 -0
  193. package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +122 -0
  194. package/src/components/EventSelector/EventSelector.tsx +1 -1
  195. package/src/components/Header/Header.test.tsx +35 -1
  196. package/src/components/Header/Header.tsx +3 -1
  197. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -3
  198. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +24 -4
  199. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +3 -2
  200. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +220 -0
  201. package/src/hooks/__tests__/useIsMobile.unit.test.ts +117 -0
  202. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +295 -0
  203. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +29 -19
  204. package/src/hooks/__tests__/useRBAC.unit.test.ts +7 -3
  205. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +115 -19
  206. package/src/hooks/useEventTheme.test.ts +350 -0
  207. package/src/hooks/useEventTheme.ts +1 -1
  208. package/src/hooks/useEvents.ts +61 -0
  209. package/src/hooks/useOrganisationSecurity.test.ts +4 -4
  210. package/src/hooks/useOrganisationSecurity.ts +2 -2
  211. package/src/hooks/useOrganisations.ts +64 -0
  212. package/src/hooks/useSecureDataAccess.test.ts +9 -5
  213. package/src/hooks/useSecureDataAccess.ts +2 -2
  214. package/src/index.ts +18 -3
  215. package/src/providers/AuthProvider.tsx +8 -292
  216. package/src/providers/EventProvider.tsx +15 -425
  217. package/src/providers/InactivityProvider.tsx +8 -231
  218. package/src/providers/OrganisationProvider.test.simple.tsx +3 -2
  219. package/src/providers/OrganisationProvider.tsx +11 -890
  220. package/src/providers/UnifiedAuthProvider.tsx +8 -320
  221. package/src/providers/__tests__/AuthProvider.test.tsx +18 -17
  222. package/src/providers/__tests__/EventProvider.test.tsx +253 -2
  223. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +65 -0
  224. package/src/providers/__tests__/InactivityProvider.test.tsx +46 -114
  225. package/src/providers/__tests__/OrganisationProvider.test.tsx +313 -3
  226. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +383 -2
  227. package/src/providers/index.ts +8 -7
  228. package/src/providers/services/EventServiceProvider.tsx +3 -0
  229. package/src/providers/services/UnifiedAuthProvider.tsx +3 -0
  230. package/src/rbac/hooks/usePermissions.test.ts +296 -0
  231. package/src/rbac/hooks/useRBAC.test.ts +9 -5
  232. package/src/rbac/hooks/useRBAC.ts +3 -3
  233. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +688 -0
  234. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +507 -0
  235. package/src/services/AuthService.ts +19 -4
  236. package/src/services/__tests__/AuthService.test.ts +288 -0
  237. package/src/styles/core.css +2 -0
  238. package/src/types/__tests__/guards.test.ts +246 -0
  239. package/src/types/guards.ts +1 -0
  240. package/src/types/organisation.ts +3 -2
  241. package/src/validation/__tests__/sanitization.unit.test.ts +250 -0
  242. package/src/validation/__tests__/schemaUtils.unit.test.ts +451 -0
  243. package/src/validation/__tests__/user.unit.test.ts +440 -0
  244. package/dist/RBACProvider-BO4ilsQB.d.ts +0 -63
  245. package/dist/UnifiedAuthProvider-D02AMXgO.d.ts +0 -103
  246. package/dist/chunk-3SP4P7NS.js.map +0 -1
  247. package/dist/chunk-B5LK25HV.js +0 -953
  248. package/dist/chunk-B5LK25HV.js.map +0 -1
  249. package/dist/chunk-BKVGJVUR.js.map +0 -1
  250. package/dist/chunk-C5Q5LRU5.js +0 -5691
  251. package/dist/chunk-C5Q5LRU5.js.map +0 -1
  252. package/dist/chunk-CDDYJCYU.js +0 -79
  253. package/dist/chunk-CDDYJCYU.js.map +0 -1
  254. package/dist/chunk-DG5Z55HH.js.map +0 -1
  255. package/dist/chunk-H2TNUICK.js.map +0 -1
  256. package/dist/chunk-IHMMNKNA.js.map +0 -1
  257. package/dist/chunk-LVQ26TCN.js.map +0 -1
  258. package/dist/chunk-ORSMVXO2.js.map +0 -1
  259. package/dist/chunk-UJMCGBLS.js.map +0 -1
  260. package/dist/chunk-V6BHACCH.js +0 -17
  261. package/dist/chunk-V6BHACCH.js.map +0 -1
  262. package/dist/rbac/cli/policy-manager.js +0 -278
  263. package/dist/rbac/cli/policy-manager.js.map +0 -1
  264. package/docs/api/interfaces/EventContextType.md +0 -96
  265. package/docs/api/interfaces/EventProviderProps.md +0 -19
  266. package/src/providers/OrganisationProvider.test.tsx +0 -164
  267. package/src/providers/UnifiedAuthProvider.test.tsx +0 -124
  268. package/src/providers/__tests__/AuthProvider.test.tsx.backup +0 -771
  269. package/src/providers/__tests__/EventProvider.test.tsx.backup +0 -824
  270. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +0 -820
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +0 -911
  272. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +0 -166
  273. package/src/rbac/cli/__tests__/policy-manager.test.ts +0 -339
  274. package/src/rbac/cli/policy-manager.ts +0 -443
  275. package/dist/{DataTable-2QR5TER5.js.map → DataTable-HWZQGASI.js.map} +0 -0
  276. package/dist/{UnifiedAuthProvider-K4NRGXL4.js.map → UnifiedAuthProvider-3NKDOSOK.js.map} +0 -0
  277. package/dist/{validation-PM_iOaTI.d.ts → validation-D8VcbTzC.d.ts} +2 -2
  278. /package/src/utils/{appNameResolver.test.ts.backup → appNameResolver.test 2.ts} +0 -0
@@ -1,820 +0,0 @@
1
- /**
2
- * @file OrganisationProvider Tests
3
- * @description Comprehensive tests for OrganisationProvider component
4
- * @package @jmruthers/pace-core
5
- * @module Providers/__tests__
6
- * @since 0.4.0
7
- *
8
- * Comprehensive test suite for OrganisationProvider component covering all critical functionality.
9
- * Follows testing guidelines with proper structure, naming, and best practices.
10
- */
11
-
12
- import React from 'react';
13
- import { render, 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 { BrowserRouter } from 'react-router-dom';
17
- import { OrganisationProvider, useOrganisations } from '../OrganisationProvider';
18
- import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
19
-
20
- // Mock the debug logger
21
- vi.mock('../../utils/debugLogger', () => ({
22
- DebugLogger: {
23
- log: vi.fn(),
24
- },
25
- }));
26
-
27
- // Mock the organisation context utility
28
- vi.mock('../../utils/organisationContext', () => ({
29
- setOrganisationContext: vi.fn().mockResolvedValue(undefined),
30
- }));
31
-
32
- // Mock react-router-dom
33
- vi.mock('react-router-dom', () => ({
34
- BrowserRouter: ({ children }: { children: React.ReactNode }) => <div data-testid="browser-router">{children}</div>,
35
- useNavigate: () => vi.fn(),
36
- }));
37
-
38
- // Mock UnifiedAuthProvider with comprehensive state management
39
- const mockUnifiedAuthState = {
40
- supabaseClient: null as any,
41
- user: null as any,
42
- session: null as any,
43
- signOut: vi.fn(),
44
- };
45
-
46
- vi.mock('../UnifiedAuthProvider', () => ({
47
- UnifiedAuthProvider: ({ children, supabaseClient }: { children: React.ReactNode; supabaseClient?: any }) => {
48
- mockUnifiedAuthState.supabaseClient = supabaseClient;
49
- return (
50
- <div data-testid="unified-auth-provider" supabaseclient={supabaseClient} appname="test-app">
51
- {children}
52
- </div>
53
- );
54
- },
55
- useUnifiedAuth: () => ({
56
- user: mockUnifiedAuthState.user,
57
- session: mockUnifiedAuthState.session,
58
- supabase: mockUnifiedAuthState.supabaseClient,
59
- signOut: mockUnifiedAuthState.signOut,
60
- }),
61
- }));
62
-
63
- // Test component that uses the organisation context
64
- const TestComponent = () => {
65
- const org = useOrganisations();
66
-
67
- return (
68
- <div>
69
- <div data-testid="selectedOrg">{org.selectedOrganisation?.display_name || 'No organisation'}</div>
70
- <div data-testid="isLoading">{org.isLoading ? 'true' : 'false'}</div>
71
- <div data-testid="error">{org.error?.message || 'No error'}</div>
72
- <div data-testid="hasValidContext">{org.hasValidOrganisationContext ? 'true' : 'false'}</div>
73
- <div data-testid="userRole">{org.getUserRole()}</div>
74
- <div data-testid="isSecure">{org.isOrganisationSecure() ? 'true' : 'false'}</div>
75
- <div data-testid="organisationsCount">{org.organisations.length}</div>
76
- <div data-testid="membershipsCount">{org.userMemberships.length}</div>
77
- <button onClick={() => org.switchOrganisation('org-2')}>
78
- Switch Organisation
79
- </button>
80
- <button onClick={() => org.refreshOrganisations()}>
81
- Refresh Organisations
82
- </button>
83
- <button onClick={() => org.ensureOrganisationContext()}>
84
- Ensure Context
85
- </button>
86
- <button onClick={() => org.validateOrganisationAccess('org-1')}>
87
- Validate Access
88
- </button>
89
- <button onClick={() => org.getPrimaryOrganisation()}>
90
- Get Primary
91
- </button>
92
- </div>
93
- );
94
- };
95
-
96
- // Wrapper component that provides auth context
97
- const TestWrapper = ({
98
- children,
99
- supabaseClient,
100
- user = null,
101
- session = null
102
- }: {
103
- children: React.ReactNode;
104
- supabaseClient?: any;
105
- user?: any;
106
- session?: any;
107
- }) => {
108
- // Update mock state
109
- mockUnifiedAuthState.user = user;
110
- mockUnifiedAuthState.session = session;
111
- mockUnifiedAuthState.supabaseClient = supabaseClient;
112
-
113
- return (
114
- <BrowserRouter>
115
- <div data-testid="unified-auth-provider" supabaseclient={supabaseClient} appname="test-app">
116
- <OrganisationProvider>
117
- {children}
118
- </OrganisationProvider>
119
- </div>
120
- </BrowserRouter>
121
- );
122
- };
123
-
124
- describe('[component] OrganisationProvider', () => {
125
- let mockSupabaseClient: any;
126
- let mockQueryBuilder: any;
127
-
128
- const mockOrganisations = [
129
- {
130
- id: '11111111-1111-1111-1111-111111111111',
131
- name: 'test-org-1',
132
- display_name: 'Test Organisation 1',
133
- subscription_tier: 'standard',
134
- settings: {},
135
- is_active: true,
136
- parent_id: null,
137
- created_at: '2023-01-01T00:00:00Z',
138
- updated_at: '2023-01-01T00:00:00Z'
139
- },
140
- {
141
- id: '22222222-2222-2222-2222-222222222222',
142
- name: 'test-org-2',
143
- display_name: 'Test Organisation 2',
144
- subscription_tier: 'premium',
145
- settings: {},
146
- is_active: true,
147
- parent_id: null,
148
- created_at: '2023-01-01T00:00:00Z',
149
- updated_at: '2023-01-01T00:00:00Z'
150
- }
151
- ];
152
-
153
- const mockMemberships = [
154
- {
155
- id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
156
- user_id: 'user-1',
157
- organisation_id: '11111111-1111-1111-1111-111111111111',
158
- role: 'org_admin',
159
- status: 'active',
160
- granted_at: '2023-01-01T00:00:00Z',
161
- granted_by: 'admin-1',
162
- revoked_at: null,
163
- revoked_by: null,
164
- notes: null,
165
- created_at: '2023-01-01T00:00:00Z',
166
- updated_at: '2023-01-01T00:00:00Z'
167
- },
168
- {
169
- id: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
170
- user_id: 'user-1',
171
- organisation_id: '22222222-2222-2222-2222-222222222222',
172
- role: 'member',
173
- status: 'active',
174
- granted_at: '2023-01-01T00:00:00Z',
175
- granted_by: 'admin-1',
176
- revoked_at: null,
177
- revoked_by: null,
178
- notes: null,
179
- created_at: '2023-01-01T00:00:00Z',
180
- updated_at: '2023-01-01T00:00:00Z'
181
- }
182
- ];
183
-
184
- beforeEach(() => {
185
- vi.clearAllMocks();
186
-
187
- // Create mock Supabase client
188
- mockSupabaseClient = createMockSupabaseClient();
189
-
190
- // Mock RPC function for getting user organisation roles
191
- mockSupabaseClient.rpc = vi.fn().mockImplementation((functionName: string, params: any) => {
192
- if (functionName === 'data_user_organisation_roles_get') {
193
- return Promise.resolve({
194
- data: mockMemberships,
195
- error: null
196
- });
197
- }
198
- return Promise.resolve({ data: null, error: null });
199
- });
200
-
201
- // Mock database queries
202
- mockSupabaseClient.from = vi.fn((table: string) => {
203
- const createMockQueryBuilder = (data: any) => {
204
- const queryBuilder = {
205
- select: vi.fn().mockReturnThis(),
206
- eq: vi.fn().mockReturnThis(),
207
- is: vi.fn().mockReturnThis(),
208
- in: vi.fn().mockReturnThis(),
209
- order: vi.fn().mockReturnThis(),
210
- limit: vi.fn().mockReturnThis(),
211
- single: vi.fn().mockResolvedValue({ data: data[0] || null, error: null }),
212
- maybeSingle: vi.fn().mockResolvedValue({ data: data[0] || null, error: null }),
213
- then: vi.fn().mockImplementation((resolve) => {
214
- resolve({ data, error: null });
215
- }),
216
- };
217
- return queryBuilder;
218
- };
219
-
220
- if (table === 'rbac_organisation_roles') {
221
- return createMockQueryBuilder(mockMemberships);
222
- } else if (table === 'organisations') {
223
- return createMockQueryBuilder(mockOrganisations);
224
- }
225
- return createMockQueryBuilder([]);
226
- });
227
-
228
- // Mock auth state
229
- mockSupabaseClient.auth.getUser = vi.fn().mockResolvedValue({
230
- data: { user: testDataGenerators.user({ id: 'user-1' }) },
231
- error: null
232
- });
233
- mockSupabaseClient.auth.getSession = vi.fn().mockResolvedValue({
234
- data: { session: testDataGenerators.session() },
235
- error: null
236
- });
237
- mockSupabaseClient.auth.onAuthStateChange = vi.fn(() => ({
238
- data: { subscription: { unsubscribe: vi.fn() } }
239
- }));
240
- });
241
-
242
- afterEach(() => {
243
- vi.restoreAllMocks();
244
- localStorage.clear();
245
- sessionStorage.clear();
246
- });
247
-
248
- describe('Rendering', () => {
249
- it('renders children without crashing', () => {
250
- render(
251
- <TestWrapper supabaseClient={mockSupabaseClient}>
252
- <div>Test content</div>
253
- </TestWrapper>
254
- );
255
-
256
- expect(screen.getByText('Test content')).toBeInTheDocument();
257
- });
258
-
259
- it('shows loading state initially', () => {
260
- render(
261
- <TestWrapper supabaseClient={mockSupabaseClient}>
262
- <TestComponent />
263
- </TestWrapper>
264
- );
265
-
266
- expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
267
- });
268
-
269
- it('renders without supabase client', () => {
270
- render(
271
- <TestWrapper>
272
- <div>Test content</div>
273
- </TestWrapper>
274
- );
275
-
276
- expect(screen.getByText('Test content')).toBeInTheDocument();
277
- });
278
- });
279
-
280
- describe('Context Hook', () => {
281
- it('throws error when used outside provider', () => {
282
- // Suppress console.error for this test
283
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
284
-
285
- expect(() => {
286
- render(<TestComponent />);
287
- }).toThrow('useOrganisations must be used within an OrganisationProvider');
288
-
289
- consoleSpy.mockRestore();
290
- });
291
- });
292
-
293
- describe('Organisation Loading', () => {
294
- it('loads organisations successfully', async () => {
295
- const user = testDataGenerators.user({ id: 'user-1' });
296
- const session = testDataGenerators.session();
297
-
298
- render(
299
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
300
- <TestComponent />
301
- </TestWrapper>
302
- );
303
-
304
- // Should show loading initially
305
- expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
306
-
307
- // Wait for loading to complete with shorter timeout
308
- await waitFor(() => {
309
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
310
- }, { timeout: 3000 });
311
-
312
- // Should have loaded organisations
313
- expect(screen.getByTestId('organisationsCount')).toHaveTextContent('2');
314
- expect(screen.getByTestId('membershipsCount')).toHaveTextContent('2');
315
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
316
- expect(screen.getByTestId('hasValidContext')).toHaveTextContent('true');
317
- }, 10000);
318
-
319
- it('handles no organisation memberships', async () => {
320
- const user = testDataGenerators.user({ id: 'user-1' });
321
- const session = testDataGenerators.session();
322
-
323
- // Mock empty memberships
324
- mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
325
- if (functionName === 'data_user_organisation_roles_get') {
326
- return Promise.resolve({
327
- data: [],
328
- error: null
329
- });
330
- }
331
- return Promise.resolve({ data: null, error: null });
332
- });
333
-
334
- render(
335
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
336
- <TestComponent />
337
- </TestWrapper>
338
- );
339
-
340
- await waitFor(() => {
341
- expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
342
- }, { timeout: 5000 });
343
- });
344
-
345
- it('handles organisation loading errors', async () => {
346
- const user = testDataGenerators.user({ id: 'user-1' });
347
- const session = testDataGenerators.session();
348
-
349
- // Mock API error
350
- mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
351
- if (functionName === 'data_user_organisation_roles_get') {
352
- return Promise.resolve({
353
- data: null,
354
- error: new Error('Database error')
355
- });
356
- }
357
- return Promise.resolve({ data: null, error: null });
358
- });
359
-
360
- render(
361
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
362
- <TestComponent />
363
- </TestWrapper>
364
- );
365
-
366
- await waitFor(() => {
367
- expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
368
- }, { timeout: 5000 });
369
- });
370
-
371
- it('handles RPC timeout with fallback query', async () => {
372
- const user = testDataGenerators.user({ id: 'user-1' });
373
- const session = testDataGenerators.session();
374
-
375
- // Mock RPC timeout
376
- mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
377
- if (functionName === 'data_user_organisation_roles_get') {
378
- return Promise.reject(new Error('RPC call timeout after 10 seconds'));
379
- }
380
- return Promise.resolve({ data: null, error: null });
381
- });
382
-
383
- render(
384
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
385
- <TestComponent />
386
- </TestWrapper>
387
- );
388
-
389
- await waitFor(() => {
390
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
391
- }, { timeout: 5000 });
392
- });
393
-
394
- it('handles invalid organisation IDs in memberships', async () => {
395
- const user = testDataGenerators.user({ id: 'user-1' });
396
- const session = testDataGenerators.session();
397
-
398
- // Mock memberships with invalid organisation IDs
399
- const invalidMemberships = [
400
- {
401
- id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
402
- user_id: 'user-1',
403
- organisation_id: '', // Invalid empty ID
404
- role: 'org_admin',
405
- status: 'active',
406
- granted_at: '2023-01-01T00:00:00Z',
407
- granted_by: 'admin-1',
408
- revoked_at: null,
409
- revoked_by: null,
410
- notes: null,
411
- created_at: '2023-01-01T00:00:00Z',
412
- updated_at: '2023-01-01T00:00:00Z'
413
- }
414
- ];
415
-
416
- mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
417
- if (functionName === 'data_user_organisation_roles_get') {
418
- return Promise.resolve({
419
- data: invalidMemberships,
420
- error: null
421
- });
422
- }
423
- return Promise.resolve({ data: null, error: null });
424
- });
425
-
426
- render(
427
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
428
- <TestComponent />
429
- </TestWrapper>
430
- );
431
-
432
- await waitFor(() => {
433
- expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
434
- }, { timeout: 5000 });
435
- });
436
- });
437
-
438
- describe('Organisation Selection', () => {
439
- it('auto-selects primary organisation', async () => {
440
- const user = testDataGenerators.user({ id: 'user-1' });
441
- const session = testDataGenerators.session();
442
-
443
- render(
444
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
445
- <TestComponent />
446
- </TestWrapper>
447
- );
448
-
449
- await waitFor(() => {
450
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
451
- expect(screen.getByTestId('userRole')).toHaveTextContent('org_admin');
452
- }, { timeout: 5000 });
453
- });
454
-
455
- it('restores persisted organisation selection', async () => {
456
- const user = testDataGenerators.user({ id: 'user-1' });
457
- const session = testDataGenerators.session();
458
-
459
- // Set persisted organisation
460
- localStorage.setItem('pace-core-selected-organisation', JSON.stringify(mockOrganisations[1]));
461
-
462
- render(
463
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
464
- <TestComponent />
465
- </TestWrapper>
466
- );
467
-
468
- await waitFor(() => {
469
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 2');
470
- expect(screen.getByTestId('userRole')).toHaveTextContent('member');
471
- }, { timeout: 5000 });
472
- });
473
-
474
- it('handles invalid persisted organisation', async () => {
475
- const user = testDataGenerators.user({ id: 'user-1' });
476
- const session = testDataGenerators.session();
477
-
478
- // Set invalid persisted organisation
479
- localStorage.setItem('pace-core-selected-organisation', JSON.stringify({ id: 'invalid' }));
480
-
481
- render(
482
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
483
- <TestComponent />
484
- </TestWrapper>
485
- );
486
-
487
- await waitFor(() => {
488
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
489
- }, { timeout: 5000 });
490
- });
491
- });
492
-
493
- describe('Organisation Switching', () => {
494
- it('switches to valid organisation', async () => {
495
- const user = testDataGenerators.user({ id: 'user-1' });
496
- const session = testDataGenerators.session();
497
-
498
- render(
499
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
500
- <TestComponent />
501
- </TestWrapper>
502
- );
503
-
504
- await waitFor(() => {
505
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
506
- }, { timeout: 5000 });
507
-
508
- // Switch to second organisation
509
- const switchButton = screen.getByText('Switch Organisation');
510
- await userEvent.click(switchButton);
511
-
512
- await waitFor(() => {
513
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 2');
514
- expect(screen.getByTestId('userRole')).toHaveTextContent('member');
515
- });
516
- });
517
-
518
- it('handles switching to invalid organisation', async () => {
519
- const user = testDataGenerators.user({ id: 'user-1' });
520
- const session = testDataGenerators.session();
521
-
522
- render(
523
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
524
- <TestComponent />
525
- </TestWrapper>
526
- );
527
-
528
- await waitFor(() => {
529
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
530
- }, { timeout: 5000 });
531
-
532
- // Try to switch to invalid organisation
533
- const TestInvalidSwitchComponent = () => {
534
- const org = useOrganisations();
535
-
536
- const handleInvalidSwitch = async () => {
537
- try {
538
- await org.switchOrganisation('invalid-org-id');
539
- } catch (error) {
540
- // Error should be thrown
541
- }
542
- };
543
-
544
- return (
545
- <button onClick={handleInvalidSwitch}>
546
- Switch to Invalid
547
- </button>
548
- );
549
- };
550
-
551
- render(
552
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
553
- <TestInvalidSwitchComponent />
554
- </TestWrapper>
555
- );
556
-
557
- const invalidSwitchButton = screen.getByText('Switch to Invalid');
558
- await userEvent.click(invalidSwitchButton);
559
-
560
- // Should not change organisation
561
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
562
- });
563
- });
564
-
565
- describe('Security Functions', () => {
566
- it('validates organisation access', async () => {
567
- const user = testDataGenerators.user({ id: 'user-1' });
568
- const session = testDataGenerators.session();
569
-
570
- render(
571
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
572
- <TestComponent />
573
- </TestWrapper>
574
- );
575
-
576
- await waitFor(() => {
577
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
578
- }, { timeout: 5000 });
579
-
580
- const validateButton = screen.getByText('Validate Access');
581
- await userEvent.click(validateButton);
582
-
583
- // Should not throw error for valid organisation
584
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
585
- });
586
-
587
- it('ensures organisation context', async () => {
588
- const user = testDataGenerators.user({ id: 'user-1' });
589
- const session = testDataGenerators.session();
590
-
591
- render(
592
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
593
- <TestComponent />
594
- </TestWrapper>
595
- );
596
-
597
- await waitFor(() => {
598
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
599
- }, { timeout: 5000 });
600
-
601
- const ensureButton = screen.getByText('Ensure Context');
602
- await userEvent.click(ensureButton);
603
-
604
- // Should not throw error when context is available
605
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
606
- });
607
-
608
- it('checks organisation security status', async () => {
609
- const user = testDataGenerators.user({ id: 'user-1' });
610
- const session = testDataGenerators.session();
611
-
612
- render(
613
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
614
- <TestComponent />
615
- </TestWrapper>
616
- );
617
-
618
- await waitFor(() => {
619
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
620
- }, { timeout: 5000 });
621
-
622
- expect(screen.getByTestId('isSecure')).toHaveTextContent('true');
623
- });
624
-
625
- it('gets primary organisation', async () => {
626
- const user = testDataGenerators.user({ id: 'user-1' });
627
- const session = testDataGenerators.session();
628
-
629
- render(
630
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
631
- <TestComponent />
632
- </TestWrapper>
633
- );
634
-
635
- await waitFor(() => {
636
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
637
- }, { timeout: 5000 });
638
-
639
- const primaryButton = screen.getByText('Get Primary');
640
- await userEvent.click(primaryButton);
641
-
642
- // Should not throw error
643
- expect(screen.getByTestId('selectedOrg')).toHaveTextContent('Test Organisation 1');
644
- });
645
- });
646
-
647
- describe('Refresh Functionality', () => {
648
- it('refreshes organisations', async () => {
649
- const user = testDataGenerators.user({ id: 'user-1' });
650
- const session = testDataGenerators.session();
651
-
652
- render(
653
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
654
- <TestComponent />
655
- </TestWrapper>
656
- );
657
-
658
- await waitFor(() => {
659
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
660
- }, { timeout: 5000 });
661
-
662
- const refreshButton = screen.getByText('Refresh Organisations');
663
- await userEvent.click(refreshButton);
664
-
665
- // Should show loading state during refresh
666
- expect(screen.getByTestId('isLoading')).toHaveTextContent('true');
667
- });
668
- });
669
-
670
- describe('Error States', () => {
671
- it('shows error when no user', () => {
672
- render(
673
- <TestWrapper supabaseClient={mockSupabaseClient}>
674
- <TestComponent />
675
- </TestWrapper>
676
- );
677
-
678
- // Should show loading initially, then error state
679
- expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
680
- });
681
-
682
- it('shows error when no session', () => {
683
- const user = testDataGenerators.user({ id: 'user-1' });
684
-
685
- render(
686
- <TestWrapper supabaseClient={mockSupabaseClient} user={user}>
687
- <TestComponent />
688
- </TestWrapper>
689
- );
690
-
691
- // Should show loading initially, then error state
692
- expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
693
- });
694
-
695
- it('shows error when no supabase client', () => {
696
- const user = testDataGenerators.user({ id: 'user-1' });
697
- const session = testDataGenerators.session();
698
-
699
- render(
700
- <TestWrapper user={user} session={session}>
701
- <TestComponent />
702
- </TestWrapper>
703
- );
704
-
705
- // Should show loading initially, then error state
706
- expect(screen.getByText('Loading organisation context...')).toBeInTheDocument();
707
- });
708
- });
709
-
710
- describe('Context Values', () => {
711
- it('provides all required context values', async () => {
712
- const user = testDataGenerators.user({ id: 'user-1' });
713
- const session = testDataGenerators.session();
714
-
715
- render(
716
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
717
- <TestComponent />
718
- </TestWrapper>
719
- );
720
-
721
- await waitFor(() => {
722
- expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
723
- }, { timeout: 5000 });
724
-
725
- // Check all context values are available
726
- expect(screen.getByTestId('selectedOrg')).toBeInTheDocument();
727
- expect(screen.getByTestId('isLoading')).toBeInTheDocument();
728
- expect(screen.getByTestId('error')).toBeInTheDocument();
729
- expect(screen.getByTestId('hasValidContext')).toBeInTheDocument();
730
- expect(screen.getByTestId('userRole')).toBeInTheDocument();
731
- expect(screen.getByTestId('isSecure')).toBeInTheDocument();
732
- expect(screen.getByTestId('organisationsCount')).toBeInTheDocument();
733
- expect(screen.getByTestId('membershipsCount')).toBeInTheDocument();
734
- });
735
-
736
- it('provides placeholder context when no organisation', () => {
737
- const TestPlaceholderComponent = () => {
738
- const org = useOrganisations();
739
-
740
- return (
741
- <div>
742
- <div data-testid="hasPlaceholderOrg">{org.selectedOrganisation ? 'true' : 'false'}</div>
743
- <div data-testid="hasValidContext">{org.hasValidOrganisationContext ? 'true' : 'false'}</div>
744
- </div>
745
- );
746
- };
747
-
748
- render(
749
- <TestWrapper>
750
- <TestPlaceholderComponent />
751
- </TestWrapper>
752
- );
753
-
754
- // Should show placeholder values
755
- expect(screen.getByTestId('hasPlaceholderOrg')).toHaveTextContent('true');
756
- expect(screen.getByTestId('hasValidContext')).toHaveTextContent('false');
757
- });
758
- });
759
-
760
- describe('Cleanup', () => {
761
- it('handles component unmount gracefully', () => {
762
- const { unmount } = render(
763
- <TestWrapper supabaseClient={mockSupabaseClient}>
764
- <div>Test content</div>
765
- </TestWrapper>
766
- );
767
-
768
- // Should not throw errors on unmount
769
- expect(() => unmount()).not.toThrow();
770
- });
771
-
772
- it('clears cached data on unmount', () => {
773
- const user = testDataGenerators.user({ id: 'user-1' });
774
- const session = testDataGenerators.session();
775
-
776
- // Set some cached data
777
- localStorage.setItem('pace-core-selected-organisation', 'test-data');
778
-
779
- const { unmount } = render(
780
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
781
- <div>Test content</div>
782
- </TestWrapper>
783
- );
784
-
785
- unmount();
786
-
787
- // Cached data should be cleared
788
- expect(localStorage.getItem('pace-core-selected-organisation')).toBeNull();
789
- });
790
- });
791
-
792
- describe('Retry Logic', () => {
793
- it('handles retry count limits', async () => {
794
- const user = testDataGenerators.user({ id: 'user-1' });
795
- const session = testDataGenerators.session();
796
-
797
- // Mock consistent failures
798
- mockSupabaseClient.rpc.mockImplementation((functionName: string, params: any) => {
799
- if (functionName === 'data_user_organisation_roles_get') {
800
- return Promise.resolve({
801
- data: null,
802
- error: new Error('Consistent failure')
803
- });
804
- }
805
- return Promise.resolve({ data: null, error: null });
806
- });
807
-
808
- render(
809
- <TestWrapper supabaseClient={mockSupabaseClient} user={user} session={session}>
810
- <TestComponent />
811
- </TestWrapper>
812
- );
813
-
814
- // Should eventually show error state after retries
815
- await waitFor(() => {
816
- expect(screen.getByText('Organisation Access Required')).toBeInTheDocument();
817
- }, { timeout: 10000 });
818
- });
819
- });
820
- });