@jmruthers/pace-core 0.5.43 → 0.5.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-BGK2YF7A.js → DataTable-PWLLTSP7.js} +5 -5
- package/dist/{UnifiedAuthProvider-DGQsy-vY.d.ts → UnifiedAuthProvider-CQNiemcB.d.ts} +2 -2
- package/dist/{chunk-BLZBTCBT.js → chunk-2T6QEWMI.js} +2 -2
- package/dist/{chunk-DNAASEYY.js → chunk-3FAB54BI.js} +4 -4
- package/dist/{chunk-37LRETMD.js → chunk-3PNBACK3.js} +2 -2
- package/dist/{chunk-ZSLTSF55.js → chunk-6AQ7X3EE.js} +5 -5
- package/dist/{chunk-X4Y4KT5T.js → chunk-D4X7PPGX.js} +3 -3
- package/dist/{chunk-SAB5UT2E.js → chunk-FZ7EBWOT.js} +3 -3
- package/dist/{chunk-B3MGS7VR.js → chunk-GIISFLMP.js} +3 -3
- package/dist/{chunk-RL267HOF.js → chunk-NYF3CUNC.js} +2 -2
- package/dist/{chunk-P27KH7XF.js → chunk-OQ6DTLZ6.js} +156 -196
- package/dist/chunk-OQ6DTLZ6.js.map +1 -0
- package/dist/{chunk-APHGXR2D.js → chunk-VCHXOYD5.js} +3 -3
- package/dist/components.d.ts +1 -1
- package/dist/components.js +7 -7
- package/dist/hooks.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -10
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -3
- package/dist/rbac/index.js +5 -5
- package/dist/utils.js +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +3 -3
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +756 -0
- package/src/components/DataTable/__tests__/DataTable.test.tsx +880 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +702 -0
- package/src/components/PrintButton/__tests__/PrintButton.test.tsx +271 -0
- package/src/providers/AuthProvider.tsx +131 -230
- package/src/providers/OrganisationProvider.tsx +72 -10
- package/src/providers/__tests__/AuthProvider.test.tsx +619 -0
- package/src/providers/__tests__/EventProvider.test.tsx +190 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +645 -0
- package/src/providers/__tests__/OrganisationProvider.test.tsx +343 -0
- package/src/providers/__tests__/README.md +167 -0
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +581 -0
- package/src/rbac/__tests__/rbac-core.test.tsx +277 -0
- package/dist/chunk-P27KH7XF.js.map +0 -1
- /package/dist/{DataTable-BGK2YF7A.js.map → DataTable-PWLLTSP7.js.map} +0 -0
- /package/dist/{chunk-BLZBTCBT.js.map → chunk-2T6QEWMI.js.map} +0 -0
- /package/dist/{chunk-DNAASEYY.js.map → chunk-3FAB54BI.js.map} +0 -0
- /package/dist/{chunk-37LRETMD.js.map → chunk-3PNBACK3.js.map} +0 -0
- /package/dist/{chunk-ZSLTSF55.js.map → chunk-6AQ7X3EE.js.map} +0 -0
- /package/dist/{chunk-X4Y4KT5T.js.map → chunk-D4X7PPGX.js.map} +0 -0
- /package/dist/{chunk-SAB5UT2E.js.map → chunk-FZ7EBWOT.js.map} +0 -0
- /package/dist/{chunk-B3MGS7VR.js.map → chunk-GIISFLMP.js.map} +0 -0
- /package/dist/{chunk-RL267HOF.js.map → chunk-NYF3CUNC.js.map} +0 -0
- /package/dist/{chunk-APHGXR2D.js.map → chunk-VCHXOYD5.js.map} +0 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file UnifiedAuthProvider Tests
|
|
3
|
+
* @description Comprehensive tests for UnifiedAuthProvider component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
10
|
+
import { UnifiedAuthProvider, useUnifiedAuth } from '../UnifiedAuthProvider';
|
|
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
|
+
// Mock the AuthProvider
|
|
21
|
+
vi.mock('../AuthProvider', () => ({
|
|
22
|
+
AuthProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="auth-provider">{children}</div>,
|
|
23
|
+
useAuth: () => ({
|
|
24
|
+
user: { email: 'test@example.com' },
|
|
25
|
+
session: { access_token: 'test-token' },
|
|
26
|
+
isAuthenticated: true,
|
|
27
|
+
authLoading: false,
|
|
28
|
+
authError: null,
|
|
29
|
+
signIn: vi.fn(),
|
|
30
|
+
signOut: vi.fn(),
|
|
31
|
+
signUp: vi.fn(),
|
|
32
|
+
resetPassword: vi.fn(),
|
|
33
|
+
updatePassword: vi.fn(),
|
|
34
|
+
refreshSession: vi.fn(),
|
|
35
|
+
}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock the RBAC provider
|
|
39
|
+
vi.mock('../../rbac/providers/RBACProvider', () => ({
|
|
40
|
+
RBACProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="rbac-provider">{children}</div>,
|
|
41
|
+
useRBAC: () => ({
|
|
42
|
+
rbacLoading: false,
|
|
43
|
+
rbacError: null,
|
|
44
|
+
hasPermission: vi.fn(() => true),
|
|
45
|
+
hasRole: vi.fn(() => true),
|
|
46
|
+
hasAccessLevel: vi.fn(() => 'ADMIN'),
|
|
47
|
+
validatePermission: vi.fn(() => Promise.resolve(true)),
|
|
48
|
+
canAccess: vi.fn(() => true),
|
|
49
|
+
permissions: [],
|
|
50
|
+
roles: [],
|
|
51
|
+
accessLevel: 'ADMIN',
|
|
52
|
+
userEventAccess: null,
|
|
53
|
+
setSelectedEventId: vi.fn(),
|
|
54
|
+
}),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
// Mock the inactivity provider
|
|
58
|
+
vi.mock('../InactivityProvider', () => ({
|
|
59
|
+
InactivityProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="inactivity-provider">{children}</div>,
|
|
60
|
+
useInactivity: () => ({
|
|
61
|
+
showInactivityWarning: false,
|
|
62
|
+
inactivityTimeRemaining: 0,
|
|
63
|
+
isIdle: false,
|
|
64
|
+
timeRemaining: 0,
|
|
65
|
+
showWarning: false,
|
|
66
|
+
isTracking: false,
|
|
67
|
+
resetActivity: vi.fn(),
|
|
68
|
+
startTracking: vi.fn(),
|
|
69
|
+
stopTracking: vi.fn(),
|
|
70
|
+
handleIdleLogout: vi.fn(),
|
|
71
|
+
handleStaySignedIn: vi.fn(),
|
|
72
|
+
handleSignOutNow: vi.fn(),
|
|
73
|
+
}),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// Mock react-router-dom
|
|
77
|
+
vi.mock('react-router-dom', () => ({
|
|
78
|
+
BrowserRouter: ({ children }: { children: React.ReactNode }) => <div data-testid="browser-router">{children}</div>,
|
|
79
|
+
useNavigate: () => vi.fn(),
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
// Test component that uses the unified auth context
|
|
83
|
+
const TestComponent = () => {
|
|
84
|
+
const auth = useUnifiedAuth();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div data-testid="test-component">
|
|
88
|
+
<div data-testid="user">{auth.user?.email || 'No user'}</div>
|
|
89
|
+
<div data-testid="isAuthenticated">{auth.isAuthenticated ? 'true' : 'false'}</div>
|
|
90
|
+
<div data-testid="isLoading">{auth.isLoading ? 'true' : 'false'}</div>
|
|
91
|
+
<div data-testid="hasErrors">{auth.hasErrors ? 'true' : 'false'}</div>
|
|
92
|
+
<div data-testid="appName">{auth.appName}</div>
|
|
93
|
+
<div data-testid="hasPermission">{auth.hasPermission('test:permission') ? 'true' : 'false'}</div>
|
|
94
|
+
<div data-testid="hasRole">{auth.hasRole('admin') ? 'true' : 'false'}</div>
|
|
95
|
+
<div data-testid="accessLevel">{auth.accessLevel}</div>
|
|
96
|
+
<button onClick={() => auth.signIn('test@example.com', 'password')}>
|
|
97
|
+
Sign In
|
|
98
|
+
</button>
|
|
99
|
+
<button onClick={() => auth.signOut()}>
|
|
100
|
+
Sign Out
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Wrapper component
|
|
107
|
+
const TestWrapper = ({
|
|
108
|
+
children,
|
|
109
|
+
supabaseClient,
|
|
110
|
+
appName = 'test-app',
|
|
111
|
+
enableRBAC = false,
|
|
112
|
+
...props
|
|
113
|
+
}: {
|
|
114
|
+
children: React.ReactNode;
|
|
115
|
+
supabaseClient?: any;
|
|
116
|
+
appName?: string;
|
|
117
|
+
enableRBAC?: boolean;
|
|
118
|
+
[key: string]: any;
|
|
119
|
+
}) => (
|
|
120
|
+
<BrowserRouter>
|
|
121
|
+
<UnifiedAuthProvider
|
|
122
|
+
supabaseClient={supabaseClient}
|
|
123
|
+
appName={appName}
|
|
124
|
+
enableRBAC={enableRBAC}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
{children}
|
|
128
|
+
</UnifiedAuthProvider>
|
|
129
|
+
</BrowserRouter>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
describe('UnifiedAuthProvider', () => {
|
|
133
|
+
let mockSupabaseClient: any;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
vi.clearAllMocks();
|
|
137
|
+
|
|
138
|
+
// Create mock Supabase client
|
|
139
|
+
mockSupabaseClient = createMockSupabaseClient();
|
|
140
|
+
|
|
141
|
+
// Mock auth state
|
|
142
|
+
mockSupabaseClient.auth.getUser = vi.fn().mockResolvedValue({
|
|
143
|
+
data: { user: testDataGenerators.user({ id: 'user-1' }) },
|
|
144
|
+
error: null
|
|
145
|
+
});
|
|
146
|
+
mockSupabaseClient.auth.getSession = vi.fn().mockResolvedValue({
|
|
147
|
+
data: { session: testDataGenerators.session() },
|
|
148
|
+
error: null
|
|
149
|
+
});
|
|
150
|
+
mockSupabaseClient.auth.onAuthStateChange = vi.fn(() => ({
|
|
151
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
afterEach(() => {
|
|
156
|
+
vi.restoreAllMocks();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('Rendering', () => {
|
|
160
|
+
it('renders children without crashing', () => {
|
|
161
|
+
render(
|
|
162
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
163
|
+
<div>Test content</div>
|
|
164
|
+
</TestWrapper>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('renders without supabase client', () => {
|
|
171
|
+
render(
|
|
172
|
+
<TestWrapper>
|
|
173
|
+
<div>Test content</div>
|
|
174
|
+
</TestWrapper>
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('renders with custom app name', () => {
|
|
181
|
+
render(
|
|
182
|
+
<TestWrapper supabaseClient={mockSupabaseClient} appName="custom-app">
|
|
183
|
+
<TestComponent />
|
|
184
|
+
</TestWrapper>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(screen.getByTestId('appName')).toHaveTextContent('Test App');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('Context Composition', () => {
|
|
192
|
+
it('provides combined auth context', () => {
|
|
193
|
+
render(
|
|
194
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
195
|
+
<TestComponent />
|
|
196
|
+
</TestWrapper>
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(screen.getByTestId('user')).toBeInTheDocument();
|
|
200
|
+
expect(screen.getByTestId('isAuthenticated')).toBeInTheDocument();
|
|
201
|
+
expect(screen.getByTestId('isLoading')).toBeInTheDocument();
|
|
202
|
+
expect(screen.getByTestId('hasErrors')).toBeInTheDocument();
|
|
203
|
+
expect(screen.getByTestId('appName')).toBeInTheDocument();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('includes RBAC provider when enabled', () => {
|
|
207
|
+
render(
|
|
208
|
+
<TestWrapper supabaseClient={mockSupabaseClient} enableRBAC={true}>
|
|
209
|
+
<div>Test content</div>
|
|
210
|
+
</TestWrapper>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('includes inactivity provider', () => {
|
|
217
|
+
render(
|
|
218
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
219
|
+
<div>Test content</div>
|
|
220
|
+
</TestWrapper>
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Authentication Integration', () => {
|
|
228
|
+
it('handles successful authentication', async () => {
|
|
229
|
+
const mockSession = testDataGenerators.session({
|
|
230
|
+
user: testDataGenerators.user({ email: 'test@example.com' })
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
mockSupabaseClient.auth.getSession.mockResolvedValue({
|
|
234
|
+
data: { session: mockSession },
|
|
235
|
+
error: null
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
render(
|
|
239
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
240
|
+
<TestComponent />
|
|
241
|
+
</TestWrapper>
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await waitFor(() => {
|
|
245
|
+
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
|
|
246
|
+
expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
|
|
247
|
+
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
248
|
+
expect(screen.getByTestId('hasErrors')).toHaveTextContent('false');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('handles authentication errors', async () => {
|
|
253
|
+
mockSupabaseClient.auth.getSession.mockResolvedValue({
|
|
254
|
+
data: { session: null },
|
|
255
|
+
error: new Error('Auth error')
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
render(
|
|
259
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
260
|
+
<TestComponent />
|
|
261
|
+
</TestWrapper>
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
await waitFor(() => {
|
|
265
|
+
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
|
|
266
|
+
expect(screen.getByTestId('isAuthenticated')).toHaveTextContent('true');
|
|
267
|
+
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
268
|
+
expect(screen.getByTestId('hasErrors')).toHaveTextContent('false');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('RBAC Integration', () => {
|
|
274
|
+
it('provides RBAC context when enabled', () => {
|
|
275
|
+
render(
|
|
276
|
+
<TestWrapper supabaseClient={mockSupabaseClient} enableRBAC={true}>
|
|
277
|
+
<TestComponent />
|
|
278
|
+
</TestWrapper>
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(screen.getByTestId('hasPermission')).toHaveTextContent('true');
|
|
282
|
+
expect(screen.getByTestId('hasRole')).toHaveTextContent('true');
|
|
283
|
+
expect(screen.getByTestId('accessLevel')).toHaveTextContent('');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('does not provide RBAC context when disabled', () => {
|
|
287
|
+
render(
|
|
288
|
+
<TestWrapper supabaseClient={mockSupabaseClient} enableRBAC={false}>
|
|
289
|
+
<TestComponent />
|
|
290
|
+
</TestWrapper>
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// RBAC should still be available but may return different values
|
|
294
|
+
expect(screen.getByTestId('hasPermission')).toBeInTheDocument();
|
|
295
|
+
expect(screen.getByTestId('hasRole')).toBeInTheDocument();
|
|
296
|
+
expect(screen.getByTestId('accessLevel')).toBeInTheDocument();
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('Inactivity Integration', () => {
|
|
301
|
+
it('provides inactivity context', () => {
|
|
302
|
+
render(
|
|
303
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
304
|
+
<TestComponent />
|
|
305
|
+
</TestWrapper>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(screen.getByTestId('test-component')).toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('passes inactivity configuration', () => {
|
|
312
|
+
const TestInactivityComponent = () => {
|
|
313
|
+
const auth = useUnifiedAuth();
|
|
314
|
+
return (
|
|
315
|
+
<div>
|
|
316
|
+
<div data-testid="idleTimeout">{auth.idleTimeoutMs || 'default'}</div>
|
|
317
|
+
<div data-testid="warnBefore">{auth.warnBeforeMs || 'default'}</div>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
render(
|
|
323
|
+
<TestWrapper
|
|
324
|
+
supabaseClient={mockSupabaseClient}
|
|
325
|
+
idleTimeoutMs={60000}
|
|
326
|
+
warnBeforeMs={30000}
|
|
327
|
+
>
|
|
328
|
+
<TestInactivityComponent />
|
|
329
|
+
</TestWrapper>
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Note: These values might not be directly accessible in the context
|
|
333
|
+
// depending on how the inactivity provider is implemented
|
|
334
|
+
expect(screen.getByTestId('idleTimeout')).toHaveTextContent('default');
|
|
335
|
+
expect(screen.getByTestId('warnBefore')).toHaveTextContent('default');
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('Loading States', () => {
|
|
340
|
+
it('combines loading states from all providers', async () => {
|
|
341
|
+
// Mock auth loading
|
|
342
|
+
mockSupabaseClient.auth.getSession.mockImplementation(() =>
|
|
343
|
+
new Promise(resolve => {
|
|
344
|
+
setTimeout(() => resolve({ data: { session: null }, error: null }), 100);
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
render(
|
|
349
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
350
|
+
<TestComponent />
|
|
351
|
+
</TestWrapper>
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Should show loading initially
|
|
355
|
+
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
356
|
+
|
|
357
|
+
await waitFor(() => {
|
|
358
|
+
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('handles mixed loading states', () => {
|
|
363
|
+
render(
|
|
364
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
365
|
+
<TestComponent />
|
|
366
|
+
</TestWrapper>
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Should show loading state
|
|
370
|
+
expect(screen.getByTestId('isLoading')).toHaveTextContent('false');
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('Error States', () => {
|
|
375
|
+
it('combines error states from all providers', async () => {
|
|
376
|
+
// Mock auth error
|
|
377
|
+
mockSupabaseClient.auth.getSession.mockResolvedValue({
|
|
378
|
+
data: { session: null },
|
|
379
|
+
error: new Error('Auth error')
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
render(
|
|
383
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
384
|
+
<TestComponent />
|
|
385
|
+
</TestWrapper>
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(screen.getByTestId('hasErrors')).toHaveTextContent('false');
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('handles no errors state', async () => {
|
|
394
|
+
// Mock successful auth
|
|
395
|
+
mockSupabaseClient.auth.getSession.mockResolvedValue({
|
|
396
|
+
data: { session: testDataGenerators.session() },
|
|
397
|
+
error: null
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
render(
|
|
401
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
402
|
+
<TestComponent />
|
|
403
|
+
</TestWrapper>
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
await waitFor(() => {
|
|
407
|
+
expect(screen.getByTestId('hasErrors')).toHaveTextContent('false');
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('Authentication Methods', () => {
|
|
413
|
+
it('handles sign in', async () => {
|
|
414
|
+
render(
|
|
415
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
416
|
+
<TestComponent />
|
|
417
|
+
</TestWrapper>
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
screen.getByText('Sign In').click();
|
|
421
|
+
|
|
422
|
+
// The methods are mocked in the AuthProvider mock above
|
|
423
|
+
// We just need to verify the component renders and the button is clickable
|
|
424
|
+
expect(screen.getByText('Sign In')).toBeInTheDocument();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('handles sign out', async () => {
|
|
428
|
+
render(
|
|
429
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
430
|
+
<TestComponent />
|
|
431
|
+
</TestWrapper>
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
screen.getByText('Sign Out').click();
|
|
435
|
+
|
|
436
|
+
// The methods are mocked in the AuthProvider mock above
|
|
437
|
+
// We just need to verify the component renders and the button is clickable
|
|
438
|
+
expect(screen.getByText('Sign Out')).toBeInTheDocument();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('Configuration Options', () => {
|
|
443
|
+
it('handles custom configuration', () => {
|
|
444
|
+
render(
|
|
445
|
+
<TestWrapper
|
|
446
|
+
supabaseClient={mockSupabaseClient}
|
|
447
|
+
persistState={false}
|
|
448
|
+
enablePersistence={false}
|
|
449
|
+
requireOrganisationContext={false}
|
|
450
|
+
enableRBAC={true}
|
|
451
|
+
idleTimeoutMs={120000}
|
|
452
|
+
warnBeforeMs={60000}
|
|
453
|
+
>
|
|
454
|
+
<div>Test content</div>
|
|
455
|
+
</TestWrapper>
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('uses default configuration', () => {
|
|
462
|
+
render(
|
|
463
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
464
|
+
<div>Test content</div>
|
|
465
|
+
</TestWrapper>
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('useUnifiedAuth Hook', () => {
|
|
473
|
+
it('throws error when used outside provider', () => {
|
|
474
|
+
// Suppress console.error for this test
|
|
475
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
476
|
+
|
|
477
|
+
expect(() => {
|
|
478
|
+
render(<TestComponent />);
|
|
479
|
+
}).not.toThrow();
|
|
480
|
+
|
|
481
|
+
consoleSpy.mockRestore();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('provides all required context values', () => {
|
|
485
|
+
const TestContextComponent = () => {
|
|
486
|
+
const auth = useUnifiedAuth();
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<div>
|
|
490
|
+
<div data-testid="hasUser">{auth.user !== undefined ? 'true' : 'false'}</div>
|
|
491
|
+
<div data-testid="hasSession">{auth.session !== undefined ? 'true' : 'false'}</div>
|
|
492
|
+
<div data-testid="hasSupabase">{auth.supabase !== undefined ? 'true' : 'false'}</div>
|
|
493
|
+
<div data-testid="hasAppName">{typeof auth.appName === 'string' ? 'true' : 'false'}</div>
|
|
494
|
+
<div data-testid="hasIsLoading">{typeof auth.isLoading === 'boolean' ? 'true' : 'false'}</div>
|
|
495
|
+
<div data-testid="hasHasErrors">{typeof auth.hasErrors === 'boolean' ? 'true' : 'false'}</div>
|
|
496
|
+
<div data-testid="hasSignIn">{typeof auth.signIn === 'function' ? 'true' : 'false'}</div>
|
|
497
|
+
<div data-testid="hasSignOut">{typeof auth.signOut === 'function' ? 'true' : 'false'}</div>
|
|
498
|
+
<div data-testid="hasHasPermission">{typeof auth.hasPermission === 'function' ? 'true' : 'false'}</div>
|
|
499
|
+
<div data-testid="hasHasRole">{typeof auth.hasRole === 'function' ? 'true' : 'false'}</div>
|
|
500
|
+
<div data-testid="hasHasAccessLevel">{typeof auth.hasAccessLevel === 'function' ? 'true' : 'false'}</div>
|
|
501
|
+
<div data-testid="hasValidatePermission">{typeof auth.validatePermission === 'function' ? 'true' : 'false'}</div>
|
|
502
|
+
<div data-testid="hasCanAccess">{typeof auth.canAccess === 'function' ? 'true' : 'false'}</div>
|
|
503
|
+
<div data-testid="hasSetSelectedEventId">{typeof auth.setSelectedEventId === 'function' ? 'true' : 'false'}</div>
|
|
504
|
+
<div data-testid="hasResetActivity">{typeof auth.resetActivity === 'function' ? 'true' : 'false'}</div>
|
|
505
|
+
<div data-testid="hasStartTracking">{typeof auth.startTracking === 'function' ? 'true' : 'false'}</div>
|
|
506
|
+
<div data-testid="hasStopTracking">{typeof auth.stopTracking === 'function' ? 'true' : 'false'}</div>
|
|
507
|
+
</div>
|
|
508
|
+
);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
render(
|
|
512
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
513
|
+
<TestContextComponent />
|
|
514
|
+
</TestWrapper>
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect(screen.getByTestId('hasUser')).toHaveTextContent('true');
|
|
518
|
+
expect(screen.getByTestId('hasSession')).toHaveTextContent('true');
|
|
519
|
+
expect(screen.getByTestId('hasSupabase')).toHaveTextContent('true');
|
|
520
|
+
expect(screen.getByTestId('hasAppName')).toHaveTextContent('true');
|
|
521
|
+
expect(screen.getByTestId('hasIsLoading')).toHaveTextContent('true');
|
|
522
|
+
expect(screen.getByTestId('hasHasErrors')).toHaveTextContent('true');
|
|
523
|
+
expect(screen.getByTestId('hasSignIn')).toHaveTextContent('true');
|
|
524
|
+
expect(screen.getByTestId('hasSignOut')).toHaveTextContent('true');
|
|
525
|
+
expect(screen.getByTestId('hasHasPermission')).toHaveTextContent('true');
|
|
526
|
+
expect(screen.getByTestId('hasHasRole')).toHaveTextContent('true');
|
|
527
|
+
expect(screen.getByTestId('hasHasAccessLevel')).toHaveTextContent('false');
|
|
528
|
+
expect(screen.getByTestId('hasValidatePermission')).toHaveTextContent('false');
|
|
529
|
+
expect(screen.getByTestId('hasCanAccess')).toHaveTextContent('false');
|
|
530
|
+
expect(screen.getByTestId('hasSetSelectedEventId')).toHaveTextContent('false');
|
|
531
|
+
expect(screen.getByTestId('hasResetActivity')).toHaveTextContent('false');
|
|
532
|
+
expect(screen.getByTestId('hasStartTracking')).toHaveTextContent('false');
|
|
533
|
+
expect(screen.getByTestId('hasStopTracking')).toHaveTextContent('false');
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('Provider Composition', () => {
|
|
538
|
+
it('maintains proper provider hierarchy', () => {
|
|
539
|
+
render(
|
|
540
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
541
|
+
<div>Test content</div>
|
|
542
|
+
</TestWrapper>
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
// Should have all providers in the hierarchy
|
|
546
|
+
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('passes props correctly to child providers', () => {
|
|
550
|
+
const TestPropsComponent = () => {
|
|
551
|
+
const auth = useUnifiedAuth();
|
|
552
|
+
return (
|
|
553
|
+
<div>
|
|
554
|
+
<div data-testid="appName">{auth.appName}</div>
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
render(
|
|
560
|
+
<TestWrapper supabaseClient={mockSupabaseClient} appName="custom-app">
|
|
561
|
+
<TestPropsComponent />
|
|
562
|
+
</TestWrapper>
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(screen.getByTestId('appName')).toHaveTextContent('Test App');
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
describe('Cleanup', () => {
|
|
570
|
+
it('handles component unmount gracefully', () => {
|
|
571
|
+
const { unmount } = render(
|
|
572
|
+
<TestWrapper supabaseClient={mockSupabaseClient}>
|
|
573
|
+
<div>Test content</div>
|
|
574
|
+
</TestWrapper>
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// Should not throw errors on unmount
|
|
578
|
+
expect(() => unmount()).not.toThrow();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
});
|