@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,771 +0,0 @@
1
- /**
2
- * @file AuthProvider Tests
3
- * @description Comprehensive tests for AuthProvider component
4
- */
5
-
6
- import React from 'react';
7
- import { render, screen, waitFor, act } from '@testing-library/react';
8
- import userEvent from '@testing-library/user-event';
9
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
- import { AuthProvider, useAuth } from '../AuthProvider';
11
- import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
12
-
13
- // Mock the debug logger
14
- vi.mock('../../utils/debugLogger', () => ({
15
- DebugLogger: {
16
- log: vi.fn(),
17
- },
18
- }));
19
-
20
- // Test component that uses the auth context
21
- const TestComponent = () => {
22
- const auth = useAuth();
23
-
24
- return (
25
- <div>
26
- <div data-testid="user">{auth.user?.email || 'No user'}</div>
27
- <div data-testid="isAuthenticated">{auth.isAuthenticated ? 'true' : 'false'}</div>
28
- <div data-testid="authLoading">{auth.authLoading ? 'true' : 'false'}</div>
29
- <div data-testid="authError">{auth.authError?.message || 'No error'}</div>
30
- <button onClick={() => auth.signIn('test@example.com', 'password')}>
31
- Sign In
32
- </button>
33
- <button onClick={() => auth.signUp('test@example.com', 'password')}>
34
- Sign Up
35
- </button>
36
- <button onClick={() => auth.signOut()}>
37
- Sign Out
38
- </button>
39
- <button onClick={() => auth.resetPassword('test@example.com')}>
40
- Reset Password
41
- </button>
42
- <button onClick={() => auth.updatePassword('newpassword')}>
43
- Update Password
44
- </button>
45
- <button onClick={() => auth.refreshSession()}>
46
- Refresh Session
47
- </button>
48
- </div>
49
- );
50
- };
51
-
52
- describe('AuthProvider', () => {
53
- let mockSupabaseClient: any;
54
- let mockAuthStateChange: any;
55
-
56
- beforeEach(() => {
57
- vi.clearAllMocks();
58
-
59
- // Create mock auth state change listener
60
- mockAuthStateChange = vi.fn();
61
-
62
- // Create mock Supabase client
63
- mockSupabaseClient = createMockSupabaseClient();
64
- mockSupabaseClient.auth.onAuthStateChange = vi.fn(() => ({
65
- data: { subscription: { unsubscribe: vi.fn() } }
66
- }));
67
- mockSupabaseClient.auth.getSession = vi.fn();
68
- mockSupabaseClient.auth.getUser = vi.fn();
69
- });
70
-
71
- afterEach(() => {
72
- vi.restoreAllMocks();
73
- });
74
-
75
- describe('Rendering', () => {
76
- it('renders children without crashing', () => {
77
- render(
78
- <AuthProvider supabaseClient={mockSupabaseClient}>
79
- <div>Test content</div>
80
- </AuthProvider>
81
- );
82
-
83
- expect(screen.getByText('Test content')).toBeInTheDocument();
84
- });
85
-
86
- it('renders without supabase client', () => {
87
- render(
88
- <AuthProvider>
89
- <div>Test content</div>
90
- </AuthProvider>
91
- );
92
-
93
- expect(screen.getByText('Test content')).toBeInTheDocument();
94
- });
95
- });
96
-
97
- describe('Context Values', () => {
98
- it('provides initial context values', () => {
99
- render(
100
- <AuthProvider supabaseClient={mockSupabaseClient}>
101
- <TestComponent />
102
- </AuthProvider>
103
- );
104
-
105
- expect(screen.getByTestId('user')).toHaveTextContent('No user');
106
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
107
- expect(screen.getByTestId('authLoading')).toHaveTextContent('true');
108
- expect(screen.getByTestId('authError')).toHaveTextContent('No error');
109
- });
110
-
111
- it('provides supabase client in context', () => {
112
- const TestSupabaseComponent = () => {
113
- const { supabase } = useAuth();
114
- return <div data-testid="supabase">{supabase ? 'Available' : 'Not available'}</div>;
115
- };
116
-
117
- render(
118
- <AuthProvider supabaseClient={mockSupabaseClient}>
119
- <TestSupabaseComponent />
120
- </AuthProvider>
121
- );
122
-
123
- expect(screen.getByTestId('supabase')).toHaveTextContent('Available');
124
- });
125
- });
126
-
127
- describe('Authentication State Management', () => {
128
- it('handles successful session restoration', async () => {
129
- const mockSession = testDataGenerators.session({
130
- user: testDataGenerators.user({ email: 'test@example.com' })
131
- });
132
-
133
- mockSupabaseClient.auth.getSession.mockResolvedValue({
134
- data: { session: mockSession },
135
- error: null
136
- });
137
-
138
- render(
139
- <AuthProvider supabaseClient={mockSupabaseClient}>
140
- <TestComponent />
141
- </AuthProvider>
142
- );
143
-
144
- await waitFor(() => {
145
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
146
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
147
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
148
- });
149
- });
150
-
151
- it('handles session restoration with error', async () => {
152
- const mockError = new Error('Session error');
153
- mockSupabaseClient.auth.getSession.mockResolvedValue({
154
- data: { session: null },
155
- error: mockError
156
- });
157
-
158
- render(
159
- <AuthProvider supabaseClient={mockSupabaseClient}>
160
- <TestComponent />
161
- </AuthProvider>
162
- );
163
-
164
- await waitFor(() => {
165
- expect(screen.getByTestId('user')).toHaveTextContent('No user');
166
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
167
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
168
- });
169
- });
170
-
171
- it('handles user without session', async () => {
172
- const mockUser = testDataGenerators.user({ email: 'test@example.com' });
173
-
174
- mockSupabaseClient.auth.getSession.mockResolvedValue({
175
- data: { session: null },
176
- error: null
177
- });
178
- mockSupabaseClient.auth.getUser.mockResolvedValue({
179
- data: { user: mockUser },
180
- error: null
181
- });
182
-
183
- render(
184
- <AuthProvider supabaseClient={mockSupabaseClient}>
185
- <TestComponent />
186
- </AuthProvider>
187
- );
188
-
189
- await waitFor(() => {
190
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
191
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
192
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
193
- });
194
- });
195
-
196
- it('handles auth state changes', async () => {
197
- const mockSession = testDataGenerators.session({
198
- user: testDataGenerators.user({ email: 'test@example.com' })
199
- });
200
-
201
- // Mock the auth state change callback
202
- let authStateCallback: any;
203
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
204
- authStateCallback = callback;
205
- return { data: { subscription: { unsubscribe: vi.fn() } } };
206
- });
207
-
208
- render(
209
- <AuthProvider supabaseClient={mockSupabaseClient}>
210
- <TestComponent />
211
- </AuthProvider>
212
- );
213
-
214
- // Simulate SIGNED_IN event
215
- await act(async () => {
216
- authStateCallback('SIGNED_IN', mockSession);
217
- });
218
-
219
- await waitFor(() => {
220
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
221
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
222
- });
223
- });
224
-
225
- it('handles SIGNED_OUT event', async () => {
226
- // Mock the auth state change callback
227
- let authStateCallback: any;
228
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
229
- authStateCallback = callback;
230
- return { data: { subscription: { unsubscribe: vi.fn() } } };
231
- });
232
-
233
- render(
234
- <AuthProvider supabaseClient={mockSupabaseClient}>
235
- <TestComponent />
236
- </AuthProvider>
237
- );
238
-
239
- // Simulate SIGNED_OUT event
240
- await act(async () => {
241
- authStateCallback('SIGNED_OUT', null);
242
- });
243
-
244
- await waitFor(() => {
245
- expect(screen.getByTestId('user')).toHaveTextContent('No user');
246
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
247
- });
248
- });
249
- });
250
-
251
- describe('Authentication Methods', () => {
252
- beforeEach(() => {
253
- mockSupabaseClient.auth.signInWithPassword = vi.fn();
254
- mockSupabaseClient.auth.signUp = vi.fn();
255
- mockSupabaseClient.auth.signOut = vi.fn();
256
- mockSupabaseClient.auth.resetPasswordForEmail = vi.fn();
257
- mockSupabaseClient.auth.updateUser = vi.fn();
258
- mockSupabaseClient.auth.refreshSession = vi.fn();
259
- });
260
-
261
- it('handles sign in with password', async () => {
262
- const mockResult = { data: { user: testDataGenerators.user() }, error: null };
263
- mockSupabaseClient.auth.signInWithPassword.mockResolvedValue(mockResult);
264
-
265
- render(
266
- <AuthProvider supabaseClient={mockSupabaseClient}>
267
- <TestComponent />
268
- </AuthProvider>
269
- );
270
-
271
- // Use fireEvent instead of userEvent to avoid CSS issues
272
- const signInButton = screen.getByText('Sign In');
273
- signInButton.click();
274
-
275
- expect(mockSupabaseClient.auth.signInWithPassword).toHaveBeenCalledWith({
276
- email: 'test@example.com',
277
- password: 'password'
278
- });
279
- });
280
-
281
- it('handles sign in without password', async () => {
282
- const mockResult = { data: { user: testDataGenerators.user() }, error: null };
283
- mockSupabaseClient.auth.signInWithPassword.mockResolvedValue(mockResult);
284
-
285
- // Create a test component that calls signIn without password
286
- const TestNoPasswordComponent = () => {
287
- const { signIn } = useAuth();
288
-
289
- const handleSignIn = () => {
290
- signIn('test@example.com'); // No password provided
291
- };
292
-
293
- return (
294
- <button onClick={handleSignIn}>Sign In No Password</button>
295
- );
296
- };
297
-
298
- render(
299
- <AuthProvider supabaseClient={mockSupabaseClient}>
300
- <TestNoPasswordComponent />
301
- </AuthProvider>
302
- );
303
-
304
- screen.getByText('Sign In No Password').click();
305
-
306
- expect(mockSupabaseClient.auth.signInWithPassword).toHaveBeenCalledWith({
307
- email: 'test@example.com',
308
- password: ''
309
- });
310
- });
311
-
312
- it('handles sign up', async () => {
313
- const mockResult = { data: { user: testDataGenerators.user() }, error: null };
314
- mockSupabaseClient.auth.signUp.mockResolvedValue(mockResult);
315
-
316
- render(
317
- <AuthProvider supabaseClient={mockSupabaseClient}>
318
- <TestComponent />
319
- </AuthProvider>
320
- );
321
-
322
- screen.getByText('Sign Up').click();
323
-
324
- expect(mockSupabaseClient.auth.signUp).toHaveBeenCalledWith({
325
- email: 'test@example.com',
326
- password: 'password'
327
- });
328
- });
329
-
330
- it('handles sign out', async () => {
331
- const mockResult = { error: null };
332
- mockSupabaseClient.auth.signOut.mockResolvedValue(mockResult);
333
-
334
- render(
335
- <AuthProvider supabaseClient={mockSupabaseClient}>
336
- <TestComponent />
337
- </AuthProvider>
338
- );
339
-
340
- screen.getByText('Sign Out').click();
341
-
342
- expect(mockSupabaseClient.auth.signOut).toHaveBeenCalled();
343
- });
344
-
345
- it('handles password reset', async () => {
346
- const mockResult = { error: null };
347
- mockSupabaseClient.auth.resetPasswordForEmail.mockResolvedValue(mockResult);
348
-
349
- render(
350
- <AuthProvider supabaseClient={mockSupabaseClient}>
351
- <TestComponent />
352
- </AuthProvider>
353
- );
354
-
355
- screen.getByText('Reset Password').click();
356
-
357
- expect(mockSupabaseClient.auth.resetPasswordForEmail).toHaveBeenCalledWith('test@example.com');
358
- });
359
-
360
- it('handles password update', async () => {
361
- const mockResult = { error: null };
362
- mockSupabaseClient.auth.updateUser.mockResolvedValue(mockResult);
363
-
364
- render(
365
- <AuthProvider supabaseClient={mockSupabaseClient}>
366
- <TestComponent />
367
- </AuthProvider>
368
- );
369
-
370
- screen.getByText('Update Password').click();
371
-
372
- expect(mockSupabaseClient.auth.updateUser).toHaveBeenCalledWith({
373
- password: 'newpassword'
374
- });
375
- });
376
-
377
- it('handles session refresh', async () => {
378
- const mockResult = { error: null };
379
- mockSupabaseClient.auth.refreshSession.mockResolvedValue(mockResult);
380
-
381
- render(
382
- <AuthProvider supabaseClient={mockSupabaseClient}>
383
- <TestComponent />
384
- </AuthProvider>
385
- );
386
-
387
- screen.getByText('Refresh Session').click();
388
-
389
- expect(mockSupabaseClient.auth.refreshSession).toHaveBeenCalled();
390
- });
391
- });
392
-
393
- describe('Error Handling', () => {
394
- it('handles sign in errors', async () => {
395
- const mockError = new Error('Invalid credentials');
396
- mockSupabaseClient.auth.signInWithPassword = vi.fn().mockResolvedValue({
397
- data: null,
398
- error: mockError
399
- });
400
-
401
- const TestErrorComponent = () => {
402
- const { signIn } = useAuth();
403
- const [error, setError] = React.useState<any>(null);
404
-
405
- const handleSignIn = async () => {
406
- const result = await signIn('test@example.com', 'password');
407
- setError(result.error);
408
- };
409
-
410
- return (
411
- <div>
412
- <button onClick={handleSignIn}>Sign In</button>
413
- <div data-testid="error">{error?.message || 'No error'}</div>
414
- </div>
415
- );
416
- };
417
-
418
- render(
419
- <AuthProvider supabaseClient={mockSupabaseClient}>
420
- <TestErrorComponent />
421
- </AuthProvider>
422
- );
423
-
424
- screen.getByText('Sign In').click();
425
-
426
- await waitFor(() => {
427
- expect(screen.getByTestId('error')).toHaveTextContent('Invalid credentials');
428
- });
429
- });
430
-
431
- it('handles missing supabase client', async () => {
432
- const TestErrorComponent = () => {
433
- const { signIn } = useAuth();
434
- const [error, setError] = React.useState<any>(null);
435
-
436
- const handleSignIn = async () => {
437
- const result = await signIn('test@example.com', 'password');
438
- setError(result.error);
439
- };
440
-
441
- return (
442
- <div>
443
- <button onClick={handleSignIn}>Sign In</button>
444
- <div data-testid="error">{error?.message || 'No error'}</div>
445
- </div>
446
- );
447
- };
448
-
449
- render(
450
- <AuthProvider>
451
- <TestErrorComponent />
452
- </AuthProvider>
453
- );
454
-
455
- screen.getByText('Sign In').click();
456
-
457
- await waitFor(() => {
458
- expect(screen.getByTestId('error')).toHaveTextContent('Supabase client not available');
459
- });
460
- });
461
-
462
- it('handles auth state change errors gracefully', async () => {
463
- // Mock the auth state change callback
464
- let authStateCallback: any;
465
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
466
- authStateCallback = callback;
467
- return { data: { subscription: { unsubscribe: vi.fn() } } };
468
- });
469
-
470
- render(
471
- <AuthProvider supabaseClient={mockSupabaseClient}>
472
- <TestComponent />
473
- </AuthProvider>
474
- );
475
-
476
- // Simulate an error in auth state change
477
- await act(async () => {
478
- authStateCallback('ERROR', null);
479
- });
480
-
481
- // Should not crash and should set loading to false
482
- await waitFor(() => {
483
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
484
- });
485
- });
486
- });
487
-
488
- describe('useAuth Hook', () => {
489
- it('throws error when used outside provider', () => {
490
- // Suppress console.error for this test
491
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
492
-
493
- expect(() => {
494
- render(<TestComponent />);
495
- }).toThrow('useAuth must be used within an AuthProvider');
496
-
497
- consoleSpy.mockRestore();
498
- });
499
-
500
- it('provides all required context values', () => {
501
- const TestContextComponent = () => {
502
- const auth = useAuth();
503
-
504
- return (
505
- <div>
506
- <div data-testid="hasUser">{auth.user !== undefined ? 'true' : 'false'}</div>
507
- <div data-testid="hasSession">{auth.session !== undefined ? 'true' : 'false'}</div>
508
- <div data-testid="hasSupabase">{auth.supabase !== undefined ? 'true' : 'false'}</div>
509
- <div data-testid="hasSignIn">{typeof auth.signIn === 'function' ? 'true' : 'false'}</div>
510
- <div data-testid="hasSignUp">{typeof auth.signUp === 'function' ? 'true' : 'false'}</div>
511
- <div data-testid="hasSignOut">{typeof auth.signOut === 'function' ? 'true' : 'false'}</div>
512
- <div data-testid="hasResetPassword">{typeof auth.resetPassword === 'function' ? 'true' : 'false'}</div>
513
- <div data-testid="hasUpdatePassword">{typeof auth.updatePassword === 'function' ? 'true' : 'false'}</div>
514
- <div data-testid="hasRefreshSession">{typeof auth.refreshSession === 'function' ? 'true' : 'false'}</div>
515
- </div>
516
- );
517
- };
518
-
519
- render(
520
- <AuthProvider supabaseClient={mockSupabaseClient}>
521
- <TestContextComponent />
522
- </AuthProvider>
523
- );
524
-
525
- expect(screen.getByTestId('hasUser')).toHaveTextContent('true');
526
- expect(screen.getByTestId('hasSession')).toHaveTextContent('true');
527
- expect(screen.getByTestId('hasSupabase')).toHaveTextContent('true');
528
- expect(screen.getByTestId('hasSignIn')).toHaveTextContent('true');
529
- expect(screen.getByTestId('hasSignUp')).toHaveTextContent('true');
530
- expect(screen.getByTestId('hasSignOut')).toHaveTextContent('true');
531
- expect(screen.getByTestId('hasResetPassword')).toHaveTextContent('true');
532
- expect(screen.getByTestId('hasUpdatePassword')).toHaveTextContent('true');
533
- expect(screen.getByTestId('hasRefreshSession')).toHaveTextContent('true');
534
- });
535
- });
536
-
537
- describe('Cleanup', () => {
538
- it('unsubscribes from auth state changes on unmount', () => {
539
- const unsubscribe = vi.fn();
540
- mockSupabaseClient.auth.onAuthStateChange.mockReturnValue({
541
- data: { subscription: { unsubscribe } }
542
- });
543
-
544
- const { unmount } = render(
545
- <AuthProvider supabaseClient={mockSupabaseClient}>
546
- <div>Test</div>
547
- </AuthProvider>
548
- );
549
-
550
- unmount();
551
- expect(unsubscribe).toHaveBeenCalledTimes(1);
552
- });
553
-
554
- it('handles component unmount during async operations', async () => {
555
- let resolvePromise: any;
556
- const promise = new Promise(resolve => {
557
- resolvePromise = resolve;
558
- });
559
-
560
- mockSupabaseClient.auth.getSession.mockReturnValue(promise);
561
-
562
- const { unmount } = render(
563
- <AuthProvider supabaseClient={mockSupabaseClient}>
564
- <TestComponent />
565
- </AuthProvider>
566
- );
567
-
568
- // Unmount before async operation completes
569
- unmount();
570
-
571
- // Resolve the promise after unmount
572
- resolvePromise({ data: { session: null }, error: null });
573
-
574
- // Should not cause any errors
575
- expect(true).toBe(true);
576
- });
577
- });
578
-
579
- describe('Error Suppression', () => {
580
- it('sets up error suppression handlers', () => {
581
- const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
582
-
583
- render(
584
- <AuthProvider supabaseClient={mockSupabaseClient}>
585
- <div>Test</div>
586
- </AuthProvider>
587
- );
588
-
589
- // Test that the component renders without errors
590
- expect(screen.getByText('Test')).toBeInTheDocument();
591
-
592
- consoleWarnSpy.mockRestore();
593
- });
594
- });
595
-
596
- describe('Context Value Stability', () => {
597
- it('provides stable function references', () => {
598
- const TestStabilityComponent = () => {
599
- const auth = useAuth();
600
-
601
- return (
602
- <div>
603
- <div data-testid="hasStableSignIn">{typeof auth.signIn === 'function' ? 'true' : 'false'}</div>
604
- </div>
605
- );
606
- };
607
-
608
- render(
609
- <AuthProvider supabaseClient={mockSupabaseClient}>
610
- <TestStabilityComponent />
611
- </AuthProvider>
612
- );
613
-
614
- expect(screen.getByTestId('hasStableSignIn')).toHaveTextContent('true');
615
- });
616
-
617
- it('provides error alias for backward compatibility', () => {
618
- const TestErrorAliasComponent = () => {
619
- const auth = useAuth();
620
- return (
621
- <div>
622
- <div data-testid="hasErrorAlias">{auth.error !== undefined ? 'true' : 'false'}</div>
623
- <div data-testid="errorAliasMatch">{auth.error === auth.authError ? 'true' : 'false'}</div>
624
- </div>
625
- );
626
- };
627
-
628
- render(
629
- <AuthProvider supabaseClient={mockSupabaseClient}>
630
- <TestErrorAliasComponent />
631
- </AuthProvider>
632
- );
633
-
634
- expect(screen.getByTestId('hasErrorAlias')).toHaveTextContent('true');
635
- expect(screen.getByTestId('errorAliasMatch')).toHaveTextContent('true');
636
- });
637
- });
638
-
639
- describe('Authentication State Edge Cases', () => {
640
- it('handles user without session correctly', async () => {
641
- const mockUser = testDataGenerators.user({ email: 'test@example.com' });
642
-
643
- mockSupabaseClient.auth.getSession.mockResolvedValue({
644
- data: { session: null },
645
- error: null
646
- });
647
- mockSupabaseClient.auth.getUser.mockResolvedValue({
648
- data: { user: mockUser },
649
- error: null
650
- });
651
-
652
- render(
653
- <AuthProvider supabaseClient={mockSupabaseClient}>
654
- <TestComponent />
655
- </AuthProvider>
656
- );
657
-
658
- await waitFor(() => {
659
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
660
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
661
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
662
- });
663
- });
664
-
665
- it('handles session without user correctly', async () => {
666
- const mockSession = testDataGenerators.session({
667
- user: null as any
668
- });
669
-
670
- mockSupabaseClient.auth.getSession.mockResolvedValue({
671
- data: { session: mockSession },
672
- error: null
673
- });
674
-
675
- render(
676
- <AuthProvider supabaseClient={mockSupabaseClient}>
677
- <TestComponent />
678
- </AuthProvider>
679
- );
680
-
681
- await waitFor(() => {
682
- expect(screen.getByTestId('user')).toHaveTextContent('No user');
683
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
684
- expect(screen.getByTestId('authLoading')).toHaveTextContent('false');
685
- });
686
- });
687
-
688
- it('handles TOKEN_REFRESHED event', async () => {
689
- const mockSession = testDataGenerators.session({
690
- user: testDataGenerators.user({ email: 'test@example.com' })
691
- });
692
-
693
- // Mock the auth state change callback
694
- let authStateCallback: any;
695
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
696
- authStateCallback = callback;
697
- return { data: { subscription: { unsubscribe: vi.fn() } } };
698
- });
699
-
700
- render(
701
- <AuthProvider supabaseClient={mockSupabaseClient}>
702
- <TestComponent />
703
- </AuthProvider>
704
- );
705
-
706
- // Simulate TOKEN_REFRESHED event
707
- await act(async () => {
708
- authStateCallback('TOKEN_REFRESHED', mockSession);
709
- });
710
-
711
- await waitFor(() => {
712
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
713
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
714
- });
715
- });
716
-
717
- it('handles INITIAL_SESSION event', async () => {
718
- const mockSession = testDataGenerators.session({
719
- user: testDataGenerators.user({ email: 'test@example.com' })
720
- });
721
-
722
- // Mock the auth state change callback
723
- let authStateCallback: any;
724
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
725
- authStateCallback = callback;
726
- return { data: { subscription: { unsubscribe: vi.fn() } } };
727
- });
728
-
729
- render(
730
- <AuthProvider supabaseClient={mockSupabaseClient}>
731
- <TestComponent />
732
- </AuthProvider>
733
- );
734
-
735
- // Simulate INITIAL_SESSION event
736
- await act(async () => {
737
- authStateCallback('INITIAL_SESSION', mockSession);
738
- });
739
-
740
- await waitFor(() => {
741
- expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
742
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
743
- });
744
- });
745
-
746
- it('handles auth state change with null session', async () => {
747
- // Mock the auth state change callback
748
- let authStateCallback: any;
749
- mockSupabaseClient.auth.onAuthStateChange.mockImplementation((callback: any) => {
750
- authStateCallback = callback;
751
- return { data: { subscription: { unsubscribe: vi.fn() } } };
752
- });
753
-
754
- render(
755
- <AuthProvider supabaseClient={mockSupabaseClient}>
756
- <TestComponent />
757
- </AuthProvider>
758
- );
759
-
760
- // Simulate SIGNED_IN event with null session
761
- await act(async () => {
762
- authStateCallback('SIGNED_IN', null);
763
- });
764
-
765
- await waitFor(() => {
766
- expect(screen.getByTestId('user')).toHaveTextContent('No user');
767
- expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('false');
768
- });
769
- });
770
- });
771
- });