@jmruthers/pace-core 0.5.117 → 0.5.119

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 (167) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-XN2LYHDI.js → chunk-B4GZ2BXO.js} +27 -8
  4. package/dist/{chunk-XN2LYHDI.js.map → chunk-B4GZ2BXO.js.map} +1 -1
  5. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  6. package/dist/chunk-BHWIUEYH.js.map +1 -0
  7. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  8. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  9. package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
  10. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  11. package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
  12. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  13. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  14. package/dist/{chunk-IZXS7RZK.js → chunk-TDNI6ZWL.js} +5 -5
  15. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  16. package/dist/components.js +8 -8
  17. package/dist/hooks.js +7 -7
  18. package/dist/index.js +11 -11
  19. package/dist/providers.js +2 -2
  20. package/dist/rbac/index.js +7 -7
  21. package/dist/utils.js +1 -1
  22. package/docs/api/classes/ColumnFactory.md +1 -1
  23. package/docs/api/classes/ErrorBoundary.md +1 -1
  24. package/docs/api/classes/InvalidScopeError.md +1 -1
  25. package/docs/api/classes/MissingUserContextError.md +1 -1
  26. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  27. package/docs/api/classes/PermissionDeniedError.md +1 -1
  28. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  29. package/docs/api/classes/RBACAuditManager.md +1 -1
  30. package/docs/api/classes/RBACCache.md +1 -1
  31. package/docs/api/classes/RBACEngine.md +1 -1
  32. package/docs/api/classes/RBACError.md +1 -1
  33. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  34. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  35. package/docs/api/classes/StorageUtils.md +1 -1
  36. package/docs/api/enums/FileCategory.md +1 -1
  37. package/docs/api/interfaces/AggregateConfig.md +1 -1
  38. package/docs/api/interfaces/ButtonProps.md +1 -1
  39. package/docs/api/interfaces/CardProps.md +1 -1
  40. package/docs/api/interfaces/ColorPalette.md +1 -1
  41. package/docs/api/interfaces/ColorShade.md +1 -1
  42. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  43. package/docs/api/interfaces/DataRecord.md +1 -1
  44. package/docs/api/interfaces/DataTableAction.md +1 -1
  45. package/docs/api/interfaces/DataTableColumn.md +1 -1
  46. package/docs/api/interfaces/DataTableProps.md +1 -1
  47. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  48. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  49. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  50. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  51. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  52. package/docs/api/interfaces/FileMetadata.md +1 -1
  53. package/docs/api/interfaces/FileReference.md +1 -1
  54. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  55. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  56. package/docs/api/interfaces/FileUploadProps.md +1 -1
  57. package/docs/api/interfaces/FooterProps.md +1 -1
  58. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  59. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  60. package/docs/api/interfaces/InputProps.md +1 -1
  61. package/docs/api/interfaces/LabelProps.md +1 -1
  62. package/docs/api/interfaces/LoginFormProps.md +1 -1
  63. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  64. package/docs/api/interfaces/NavigationContextType.md +1 -1
  65. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  66. package/docs/api/interfaces/NavigationItem.md +1 -1
  67. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  68. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  69. package/docs/api/interfaces/Organisation.md +1 -1
  70. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  71. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  72. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  73. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  74. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  75. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  76. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  77. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  78. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  79. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  80. package/docs/api/interfaces/PaletteData.md +1 -1
  81. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  82. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  83. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  84. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  85. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  86. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  87. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  88. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  89. package/docs/api/interfaces/RBACConfig.md +1 -1
  90. package/docs/api/interfaces/RBACLogger.md +1 -1
  91. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  92. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  93. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  94. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  95. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  96. package/docs/api/interfaces/RouteConfig.md +1 -1
  97. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  98. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  99. package/docs/api/interfaces/StorageConfig.md +1 -1
  100. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  101. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  102. package/docs/api/interfaces/StorageListOptions.md +1 -1
  103. package/docs/api/interfaces/StorageListResult.md +1 -1
  104. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  105. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  106. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  107. package/docs/api/interfaces/StyleImport.md +1 -1
  108. package/docs/api/interfaces/SwitchProps.md +1 -1
  109. package/docs/api/interfaces/ToastActionElement.md +1 -1
  110. package/docs/api/interfaces/ToastProps.md +1 -1
  111. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  112. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  113. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  114. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  115. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  116. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  117. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  118. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  119. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  120. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  121. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  122. package/docs/api/interfaces/UserEventAccess.md +1 -1
  123. package/docs/api/interfaces/UserMenuProps.md +1 -1
  124. package/docs/api/interfaces/UserProfile.md +1 -1
  125. package/docs/api/modules.md +2 -2
  126. package/package.json +1 -1
  127. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  128. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
  129. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  130. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  131. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  132. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  133. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  134. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  135. package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
  136. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  137. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  138. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  139. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  140. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  141. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  142. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
  143. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  144. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
  145. package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
  146. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  147. package/src/hooks/useSecureDataAccess.ts +43 -5
  148. package/src/rbac/audit-enhanced.ts +339 -0
  149. package/src/services/EventService.ts +1 -0
  150. package/src/services/__tests__/AuthService.test.ts +473 -0
  151. package/src/services/__tests__/EventService.test.ts +390 -0
  152. package/src/services/__tests__/InactivityService.test.ts +217 -0
  153. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  154. package/dist/chunk-KA3PSVNV.js.map +0 -1
  155. package/src/components/DataTable/utils/debugTools.ts +0 -609
  156. package/src/rbac/testing/index.tsx +0 -340
  157. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
  158. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  159. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  160. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  161. /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
  162. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  163. /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
  164. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  165. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  166. /package/dist/{chunk-IZXS7RZK.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  167. /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
@@ -0,0 +1,372 @@
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
+ act(() => {
125
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
126
+ });
127
+
128
+ await waitFor(() => {
129
+ expect(result.current.hasTimedOut).toBe(true);
130
+ }, { timeout: 100 });
131
+
132
+ expect(console.warn).toHaveBeenCalledWith(
133
+ '[useSessionRestoration] Session restoration timed out'
134
+ );
135
+ });
136
+
137
+ it('does not set timeout when restoration is complete', () => {
138
+ mockContext.sessionRestoration = {
139
+ isRestoring: true,
140
+ restorationComplete: true,
141
+ restorationError: null,
142
+ };
143
+
144
+ const wrapper = createWrapper(mockContext);
145
+ const { result } = renderHook(() => useSessionRestoration(), { wrapper });
146
+
147
+ expect(result.current.hasTimedOut).toBe(false);
148
+
149
+ // Advance time past timeout
150
+ act(() => {
151
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
152
+ });
153
+
154
+ // Should still be false because restoration is complete
155
+ expect(result.current.hasTimedOut).toBe(false);
156
+ expect(console.warn).not.toHaveBeenCalled();
157
+ });
158
+
159
+ it('does not set timeout when restoration has error', () => {
160
+ mockContext.sessionRestoration = {
161
+ isRestoring: true,
162
+ restorationComplete: false,
163
+ restorationError: new Error('Restoration failed'),
164
+ };
165
+
166
+ const wrapper = createWrapper(mockContext);
167
+ const { result } = renderHook(() => useSessionRestoration(), { wrapper });
168
+
169
+ expect(result.current.hasTimedOut).toBe(false);
170
+
171
+ // Advance time past timeout
172
+ act(() => {
173
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
174
+ });
175
+
176
+ // Should still be false because there's an error
177
+ expect(result.current.hasTimedOut).toBe(false);
178
+ expect(console.warn).not.toHaveBeenCalled();
179
+ });
180
+ });
181
+
182
+ describe('Timeout Cleanup', () => {
183
+ it('clears timeout when restoration completes before timeout', () => {
184
+ mockContext.sessionRestoration = {
185
+ isRestoring: true,
186
+ restorationComplete: false,
187
+ restorationError: null,
188
+ };
189
+
190
+ const wrapper = createWrapper(mockContext);
191
+ const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
192
+
193
+ // Start restoration
194
+ expect(result.current.hasTimedOut).toBe(false);
195
+
196
+ // Complete restoration before timeout
197
+ act(() => {
198
+ mockContext.sessionRestoration = {
199
+ isRestoring: false,
200
+ restorationComplete: true,
201
+ restorationError: null,
202
+ };
203
+ rerender();
204
+ });
205
+
206
+ // Advance time past timeout
207
+ act(() => {
208
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
209
+ });
210
+
211
+ // Should not have timed out
212
+ expect(result.current.hasTimedOut).toBe(false);
213
+ expect(console.warn).not.toHaveBeenCalled();
214
+ });
215
+
216
+ it('clears timeout when restoration error occurs before timeout', () => {
217
+ mockContext.sessionRestoration = {
218
+ isRestoring: true,
219
+ restorationComplete: false,
220
+ restorationError: null,
221
+ };
222
+
223
+ const wrapper = createWrapper(mockContext);
224
+ const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
225
+
226
+ // Start restoration
227
+ expect(result.current.hasTimedOut).toBe(false);
228
+
229
+ // Error occurs before timeout
230
+ act(() => {
231
+ mockContext.sessionRestoration = {
232
+ isRestoring: false,
233
+ restorationComplete: false,
234
+ restorationError: new Error('Restoration error'),
235
+ };
236
+ rerender();
237
+ });
238
+
239
+ // Advance time past timeout
240
+ act(() => {
241
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
242
+ });
243
+
244
+ // Should not have timed out
245
+ expect(result.current.hasTimedOut).toBe(false);
246
+ expect(console.warn).not.toHaveBeenCalled();
247
+ });
248
+
249
+ it('clears timeout on unmount', () => {
250
+ mockContext.sessionRestoration = {
251
+ isRestoring: true,
252
+ restorationComplete: false,
253
+ restorationError: null,
254
+ };
255
+
256
+ const wrapper = createWrapper(mockContext);
257
+ const { unmount } = renderHook(() => useSessionRestoration(), { wrapper });
258
+
259
+ // Unmount before timeout
260
+ act(() => {
261
+ unmount();
262
+ });
263
+
264
+ // Advance time past timeout
265
+ act(() => {
266
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
267
+ });
268
+
269
+ // Should not log warning after unmount
270
+ expect(console.warn).not.toHaveBeenCalled();
271
+ });
272
+ });
273
+
274
+ describe('State Updates', () => {
275
+ it('updates when restoration state changes', () => {
276
+ mockContext.sessionRestoration = {
277
+ isRestoring: false,
278
+ restorationComplete: false,
279
+ restorationError: null,
280
+ };
281
+
282
+ const wrapper = createWrapper(mockContext);
283
+ const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
284
+
285
+ expect(result.current.isRestoring).toBe(false);
286
+
287
+ // Update restoration state
288
+ act(() => {
289
+ mockContext.sessionRestoration = {
290
+ isRestoring: true,
291
+ restorationComplete: false,
292
+ restorationError: null,
293
+ };
294
+ rerender();
295
+ });
296
+
297
+ expect(result.current.isRestoring).toBe(true);
298
+ });
299
+
300
+ it('resets hasTimedOut when restoration state changes', async () => {
301
+ mockContext.sessionRestoration = {
302
+ isRestoring: true,
303
+ restorationComplete: false,
304
+ restorationError: null,
305
+ };
306
+
307
+ const wrapper = createWrapper(mockContext);
308
+ const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
309
+
310
+ // Trigger timeout
311
+ act(() => {
312
+ vi.advanceTimersByTime(SESSION_RESTORATION_TIMEOUT_MS);
313
+ });
314
+
315
+ await waitFor(() => {
316
+ expect(result.current.hasTimedOut).toBe(true);
317
+ }, { timeout: 100 });
318
+
319
+ // Reset restoration state
320
+ act(() => {
321
+ mockContext.sessionRestoration = {
322
+ isRestoring: false,
323
+ restorationComplete: false,
324
+ restorationError: null,
325
+ };
326
+ rerender();
327
+ });
328
+
329
+ // Should reset hasTimedOut
330
+ expect(result.current.hasTimedOut).toBe(false);
331
+ });
332
+ });
333
+
334
+ describe('Memoization', () => {
335
+ it('memoizes return value based on sessionRestoration and hasTimedOut', () => {
336
+ mockContext.sessionRestoration = {
337
+ isRestoring: false,
338
+ restorationComplete: false,
339
+ restorationError: null,
340
+ };
341
+
342
+ const wrapper = createWrapper(mockContext);
343
+ const { result, rerender } = renderHook(() => useSessionRestoration(), { wrapper });
344
+
345
+ const firstResult = result.current;
346
+
347
+ // Rerender without state change
348
+ rerender();
349
+
350
+ // Should be same object reference (memoized)
351
+ expect(result.current).toBe(firstResult);
352
+ });
353
+ });
354
+
355
+ describe('Error Handling', () => {
356
+ it('returns restoration error from context', () => {
357
+ const mockError = new Error('Session restoration failed');
358
+ mockContext.sessionRestoration = {
359
+ isRestoring: false,
360
+ restorationComplete: false,
361
+ restorationError: mockError,
362
+ };
363
+
364
+ const wrapper = createWrapper(mockContext);
365
+ const { result } = renderHook(() => useSessionRestoration(), { wrapper });
366
+
367
+ expect(result.current.restorationError).toEqual(mockError);
368
+ expect(result.current.hasTimedOut).toBe(false);
369
+ });
370
+ });
371
+ });
372
+