@jmruthers/pace-core 0.5.118 → 0.5.120
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-ZOAKQ3SU.js → DataTable-DGZDJUYM.js} +7 -7
- package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
- package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
- package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
- package/dist/chunk-BHWIUEYH.js.map +1 -0
- package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
- package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
- package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
- package/dist/{chunk-2GJ5GL77.js → chunk-GKHF54DI.js} +2 -2
- package/dist/chunk-GKHF54DI.js.map +1 -0
- package/dist/{chunk-UKZWNQMB.js → chunk-HFBOFZ3Z.js} +5 -18
- package/dist/chunk-HFBOFZ3Z.js.map +1 -0
- package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
- package/dist/{chunk-2LM4QQGH.js → chunk-QPI2CCBA.js} +9 -9
- package/dist/chunk-QPI2CCBA.js.map +1 -0
- package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
- package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
- package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
- package/dist/components.d.ts +1 -1
- package/dist/components.js +9 -9
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +8 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -12
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/{useToast-Cs_g32bg.d.ts → useToast-C8gR5ir4.d.ts} +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +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/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.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/DataRecord.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/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/ProtectedRouteProps.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/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.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/SwitchProps.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/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.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 +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
- package/src/components/DataTable/components/DataTableCore.tsx +5 -0
- package/src/components/DataTable/components/EditableRow.tsx +9 -18
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +616 -9
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/hooks/__tests__/index.unit.test.ts +223 -0
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
- package/src/hooks/__tests__/useEvents.unit.test.ts +251 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
- package/src/hooks/__tests__/useFocusManagement.unit.test.ts +19 -9
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +661 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +371 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +449 -30
- package/src/hooks/useSecureDataAccess.test.ts +1 -0
- package/src/hooks/useToast.ts +4 -4
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/services/EventService.ts +1 -0
- package/src/services/__tests__/AuthService.test.ts +473 -0
- package/src/services/__tests__/EventService.test.ts +390 -0
- package/src/services/__tests__/InactivityService.test.ts +217 -0
- package/src/services/__tests__/OrganisationService.test.ts +371 -0
- package/src/styles/core.css +1 -0
- package/dist/chunk-2GJ5GL77.js.map +0 -1
- package/dist/chunk-2LM4QQGH.js.map +0 -1
- package/dist/chunk-KA3PSVNV.js.map +0 -1
- package/dist/chunk-UKZWNQMB.js.map +0 -1
- package/src/components/DataTable/utils/debugTools.ts +0 -609
- package/src/rbac/testing/index.tsx +0 -340
- /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-DGZDJUYM.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
- /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
- /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
- /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
- /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
- /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
- /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
- /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
- /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useSessionRestoration Hook Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/__tests__/useSessionRestoration
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for the useSessionRestoration hook covering all critical functionality.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
11
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { useSessionRestoration } from '../useSessionRestoration';
|
|
14
|
+
import { AuthServiceContext } from '../../providers/services/AuthServiceProvider';
|
|
15
|
+
import type { SessionRestorationState } from '../../types/auth';
|
|
16
|
+
|
|
17
|
+
const SESSION_RESTORATION_TIMEOUT_MS = 5000;
|
|
18
|
+
|
|
19
|
+
describe('useSessionRestoration', () => {
|
|
20
|
+
let mockContext: { sessionRestoration: SessionRestorationState };
|
|
21
|
+
|
|
22
|
+
const createWrapper = (contextValue: typeof mockContext) => {
|
|
23
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
24
|
+
<AuthServiceContext.Provider value={contextValue as any}>
|
|
25
|
+
{children}
|
|
26
|
+
</AuthServiceContext.Provider>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
vi.useFakeTimers();
|
|
33
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
34
|
+
|
|
35
|
+
mockContext = {
|
|
36
|
+
sessionRestoration: {
|
|
37
|
+
isRestoring: false,
|
|
38
|
+
restorationComplete: false,
|
|
39
|
+
restorationError: null,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.clearAllTimers();
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Provider Context', () => {
|
|
51
|
+
it('throws error when used outside AuthServiceProvider', () => {
|
|
52
|
+
// Suppress React error boundary warnings
|
|
53
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
54
|
+
|
|
55
|
+
expect(() => {
|
|
56
|
+
renderHook(() => useSessionRestoration());
|
|
57
|
+
}).toThrow('useSessionRestoration must be used within AuthServiceProvider');
|
|
58
|
+
|
|
59
|
+
// Restore console.error after assertion
|
|
60
|
+
consoleErrorSpy.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns session restoration state from context', () => {
|
|
64
|
+
const wrapper = createWrapper(mockContext);
|
|
65
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
66
|
+
|
|
67
|
+
expect(result.current.isRestoring).toBe(false);
|
|
68
|
+
expect(result.current.restorationComplete).toBe(false);
|
|
69
|
+
expect(result.current.restorationError).toBeNull();
|
|
70
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
71
|
+
expect(result.current.timeoutMs).toBe(SESSION_RESTORATION_TIMEOUT_MS);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Initial State', () => {
|
|
76
|
+
it('returns false for hasTimedOut when not restoring', () => {
|
|
77
|
+
mockContext.sessionRestoration = {
|
|
78
|
+
isRestoring: false,
|
|
79
|
+
restorationComplete: false,
|
|
80
|
+
restorationError: null,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const wrapper = createWrapper(mockContext);
|
|
84
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
85
|
+
|
|
86
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns timeout duration', () => {
|
|
90
|
+
const wrapper = createWrapper(mockContext);
|
|
91
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
92
|
+
|
|
93
|
+
expect(result.current.timeoutMs).toBe(SESSION_RESTORATION_TIMEOUT_MS);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('Session Restoration Active', () => {
|
|
98
|
+
it('sets hasTimedOut to false when restoration starts', () => {
|
|
99
|
+
mockContext.sessionRestoration = {
|
|
100
|
+
isRestoring: true,
|
|
101
|
+
restorationComplete: false,
|
|
102
|
+
restorationError: null,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const wrapper = createWrapper(mockContext);
|
|
106
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
107
|
+
|
|
108
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('sets timeout when restoration is active', async () => {
|
|
112
|
+
mockContext.sessionRestoration = {
|
|
113
|
+
isRestoring: true,
|
|
114
|
+
restorationComplete: false,
|
|
115
|
+
restorationError: null,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const wrapper = createWrapper(mockContext);
|
|
119
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
120
|
+
|
|
121
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
122
|
+
|
|
123
|
+
// Advance time past timeout
|
|
124
|
+
await act(async () => {
|
|
125
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// With fake timers, the timeout should fire immediately
|
|
129
|
+
expect(result.current.hasTimedOut).toBe(true);
|
|
130
|
+
|
|
131
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
132
|
+
'[useSessionRestoration] Session restoration timed out'
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('does not set timeout when restoration is complete', () => {
|
|
137
|
+
mockContext.sessionRestoration = {
|
|
138
|
+
isRestoring: true,
|
|
139
|
+
restorationComplete: true,
|
|
140
|
+
restorationError: null,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const wrapper = createWrapper(mockContext);
|
|
144
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
145
|
+
|
|
146
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
147
|
+
|
|
148
|
+
// Advance time past timeout
|
|
149
|
+
act(() => {
|
|
150
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Should still be false because restoration is complete
|
|
154
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
155
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('does not set timeout when restoration has error', () => {
|
|
159
|
+
mockContext.sessionRestoration = {
|
|
160
|
+
isRestoring: true,
|
|
161
|
+
restorationComplete: false,
|
|
162
|
+
restorationError: new Error('Restoration failed'),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const wrapper = createWrapper(mockContext);
|
|
166
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
167
|
+
|
|
168
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
169
|
+
|
|
170
|
+
// Advance time past timeout
|
|
171
|
+
act(() => {
|
|
172
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Should still be false because there's an error
|
|
176
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
177
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Timeout Cleanup', () => {
|
|
182
|
+
it('clears timeout when restoration completes before timeout', () => {
|
|
183
|
+
mockContext.sessionRestoration = {
|
|
184
|
+
isRestoring: true,
|
|
185
|
+
restorationComplete: false,
|
|
186
|
+
restorationError: null,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const wrapper = createWrapper(mockContext);
|
|
190
|
+
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
191
|
+
|
|
192
|
+
// Start restoration
|
|
193
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
194
|
+
|
|
195
|
+
// Complete restoration before timeout
|
|
196
|
+
act(() => {
|
|
197
|
+
mockContext.sessionRestoration = {
|
|
198
|
+
isRestoring: false,
|
|
199
|
+
restorationComplete: true,
|
|
200
|
+
restorationError: null,
|
|
201
|
+
};
|
|
202
|
+
rerender();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Advance time past timeout
|
|
206
|
+
act(() => {
|
|
207
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Should not have timed out
|
|
211
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
212
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('clears timeout when restoration error occurs before timeout', () => {
|
|
216
|
+
mockContext.sessionRestoration = {
|
|
217
|
+
isRestoring: true,
|
|
218
|
+
restorationComplete: false,
|
|
219
|
+
restorationError: null,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const wrapper = createWrapper(mockContext);
|
|
223
|
+
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
224
|
+
|
|
225
|
+
// Start restoration
|
|
226
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
227
|
+
|
|
228
|
+
// Error occurs before timeout
|
|
229
|
+
act(() => {
|
|
230
|
+
mockContext.sessionRestoration = {
|
|
231
|
+
isRestoring: false,
|
|
232
|
+
restorationComplete: false,
|
|
233
|
+
restorationError: new Error('Restoration error'),
|
|
234
|
+
};
|
|
235
|
+
rerender();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Advance time past timeout
|
|
239
|
+
act(() => {
|
|
240
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Should not have timed out
|
|
244
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
245
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('clears timeout on unmount', () => {
|
|
249
|
+
mockContext.sessionRestoration = {
|
|
250
|
+
isRestoring: true,
|
|
251
|
+
restorationComplete: false,
|
|
252
|
+
restorationError: null,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const wrapper = createWrapper(mockContext);
|
|
256
|
+
const { unmount } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
257
|
+
|
|
258
|
+
// Unmount before timeout
|
|
259
|
+
act(() => {
|
|
260
|
+
unmount();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Advance time past timeout
|
|
264
|
+
act(() => {
|
|
265
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Should not log warning after unmount
|
|
269
|
+
expect(console.warn).not.toHaveBeenCalled();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('State Updates', () => {
|
|
274
|
+
it('updates when restoration state changes', () => {
|
|
275
|
+
mockContext.sessionRestoration = {
|
|
276
|
+
isRestoring: false,
|
|
277
|
+
restorationComplete: false,
|
|
278
|
+
restorationError: null,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const wrapper = createWrapper(mockContext);
|
|
282
|
+
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
283
|
+
|
|
284
|
+
expect(result.current.isRestoring).toBe(false);
|
|
285
|
+
|
|
286
|
+
// Update restoration state
|
|
287
|
+
act(() => {
|
|
288
|
+
mockContext.sessionRestoration = {
|
|
289
|
+
isRestoring: true,
|
|
290
|
+
restorationComplete: false,
|
|
291
|
+
restorationError: null,
|
|
292
|
+
};
|
|
293
|
+
rerender();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(result.current.isRestoring).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('resets hasTimedOut when restoration state changes', () => {
|
|
300
|
+
mockContext.sessionRestoration = {
|
|
301
|
+
isRestoring: true,
|
|
302
|
+
restorationComplete: false,
|
|
303
|
+
restorationError: null,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const wrapper = createWrapper(mockContext);
|
|
307
|
+
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
308
|
+
|
|
309
|
+
// Trigger timeout - advance timers
|
|
310
|
+
act(() => {
|
|
311
|
+
vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS + 100);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Verify timeout occurred (with fake timers, setTimeout fires immediately)
|
|
315
|
+
expect(result.current.hasTimedOut).toBe(true);
|
|
316
|
+
|
|
317
|
+
// Reset restoration state
|
|
318
|
+
act(() => {
|
|
319
|
+
mockContext.sessionRestoration = {
|
|
320
|
+
isRestoring: false,
|
|
321
|
+
restorationComplete: false,
|
|
322
|
+
restorationError: null,
|
|
323
|
+
};
|
|
324
|
+
rerender();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Should reset hasTimedOut immediately when state changes
|
|
328
|
+
// The useEffect will detect the change and clear the timeout, resetting hasTimedOut
|
|
329
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('Memoization', () => {
|
|
334
|
+
it('memoizes return value based on sessionRestoration and hasTimedOut', () => {
|
|
335
|
+
mockContext.sessionRestoration = {
|
|
336
|
+
isRestoring: false,
|
|
337
|
+
restorationComplete: false,
|
|
338
|
+
restorationError: null,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const wrapper = createWrapper(mockContext);
|
|
342
|
+
const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
343
|
+
|
|
344
|
+
const firstResult = result.current;
|
|
345
|
+
|
|
346
|
+
// Rerender without state change
|
|
347
|
+
rerender();
|
|
348
|
+
|
|
349
|
+
// Should be same object reference (memoized)
|
|
350
|
+
expect(result.current).toBe(firstResult);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('Error Handling', () => {
|
|
355
|
+
it('returns restoration error from context', () => {
|
|
356
|
+
const mockError = new Error('Session restoration failed');
|
|
357
|
+
mockContext.sessionRestoration = {
|
|
358
|
+
isRestoring: false,
|
|
359
|
+
restorationComplete: false,
|
|
360
|
+
restorationError: mockError,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const wrapper = createWrapper(mockContext);
|
|
364
|
+
const { result } = renderHook(() => useSessionRestoration(), { wrapper });
|
|
365
|
+
|
|
366
|
+
expect(result.current.restorationError).toEqual(mockError);
|
|
367
|
+
expect(result.current.hasTimedOut).toBe(false);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|