@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.
Files changed (181) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-DGZDJUYM.js} +7 -7
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
  4. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  5. package/dist/chunk-BHWIUEYH.js.map +1 -0
  6. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  7. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  8. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  9. package/dist/{chunk-2GJ5GL77.js → chunk-GKHF54DI.js} +2 -2
  10. package/dist/chunk-GKHF54DI.js.map +1 -0
  11. package/dist/{chunk-UKZWNQMB.js → chunk-HFBOFZ3Z.js} +5 -18
  12. package/dist/chunk-HFBOFZ3Z.js.map +1 -0
  13. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  14. package/dist/{chunk-2LM4QQGH.js → chunk-QPI2CCBA.js} +9 -9
  15. package/dist/chunk-QPI2CCBA.js.map +1 -0
  16. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  17. package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
  18. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  19. package/dist/components.d.ts +1 -1
  20. package/dist/components.js +9 -9
  21. package/dist/hooks.d.ts +1 -1
  22. package/dist/hooks.js +8 -8
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +12 -12
  25. package/dist/providers.js +2 -2
  26. package/dist/rbac/index.js +7 -7
  27. package/dist/{useToast-Cs_g32bg.d.ts → useToast-C8gR5ir4.d.ts} +2 -2
  28. package/dist/utils.js +1 -1
  29. package/docs/api/classes/ColumnFactory.md +1 -1
  30. package/docs/api/classes/ErrorBoundary.md +1 -1
  31. package/docs/api/classes/InvalidScopeError.md +1 -1
  32. package/docs/api/classes/MissingUserContextError.md +1 -1
  33. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  34. package/docs/api/classes/PermissionDeniedError.md +1 -1
  35. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  36. package/docs/api/classes/RBACAuditManager.md +1 -1
  37. package/docs/api/classes/RBACCache.md +1 -1
  38. package/docs/api/classes/RBACEngine.md +1 -1
  39. package/docs/api/classes/RBACError.md +1 -1
  40. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  41. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  42. package/docs/api/classes/StorageUtils.md +1 -1
  43. package/docs/api/enums/FileCategory.md +1 -1
  44. package/docs/api/interfaces/AggregateConfig.md +1 -1
  45. package/docs/api/interfaces/ButtonProps.md +1 -1
  46. package/docs/api/interfaces/CardProps.md +1 -1
  47. package/docs/api/interfaces/ColorPalette.md +1 -1
  48. package/docs/api/interfaces/ColorShade.md +1 -1
  49. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  50. package/docs/api/interfaces/DataRecord.md +1 -1
  51. package/docs/api/interfaces/DataTableAction.md +1 -1
  52. package/docs/api/interfaces/DataTableColumn.md +1 -1
  53. package/docs/api/interfaces/DataTableProps.md +1 -1
  54. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  55. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  56. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  57. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  58. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  59. package/docs/api/interfaces/FileMetadata.md +1 -1
  60. package/docs/api/interfaces/FileReference.md +1 -1
  61. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  62. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  63. package/docs/api/interfaces/FileUploadProps.md +1 -1
  64. package/docs/api/interfaces/FooterProps.md +1 -1
  65. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  66. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  67. package/docs/api/interfaces/InputProps.md +1 -1
  68. package/docs/api/interfaces/LabelProps.md +1 -1
  69. package/docs/api/interfaces/LoginFormProps.md +1 -1
  70. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  71. package/docs/api/interfaces/NavigationContextType.md +1 -1
  72. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  73. package/docs/api/interfaces/NavigationItem.md +1 -1
  74. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  76. package/docs/api/interfaces/Organisation.md +1 -1
  77. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  78. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  79. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  80. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  81. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  82. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  83. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  84. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  85. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  86. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  87. package/docs/api/interfaces/PaletteData.md +1 -1
  88. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  89. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  90. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  92. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  96. package/docs/api/interfaces/RBACConfig.md +1 -1
  97. package/docs/api/interfaces/RBACLogger.md +1 -1
  98. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  99. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  100. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  101. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  102. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  103. package/docs/api/interfaces/RouteConfig.md +1 -1
  104. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  105. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  106. package/docs/api/interfaces/StorageConfig.md +1 -1
  107. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  108. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  109. package/docs/api/interfaces/StorageListOptions.md +1 -1
  110. package/docs/api/interfaces/StorageListResult.md +1 -1
  111. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  112. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  113. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  114. package/docs/api/interfaces/StyleImport.md +1 -1
  115. package/docs/api/interfaces/SwitchProps.md +1 -1
  116. package/docs/api/interfaces/ToastActionElement.md +1 -1
  117. package/docs/api/interfaces/ToastProps.md +1 -1
  118. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  119. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  120. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  121. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  124. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  125. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  127. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  128. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  129. package/docs/api/interfaces/UserEventAccess.md +1 -1
  130. package/docs/api/interfaces/UserMenuProps.md +1 -1
  131. package/docs/api/interfaces/UserProfile.md +1 -1
  132. package/docs/api/modules.md +2 -2
  133. package/package.json +1 -1
  134. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  135. package/src/components/DataTable/components/DataTableCore.tsx +5 -0
  136. package/src/components/DataTable/components/EditableRow.tsx +9 -18
  137. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +616 -9
  138. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  139. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  140. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  141. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  142. package/src/components/Toast/Toast.tsx +1 -1
  143. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  144. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  145. package/src/hooks/__tests__/useEvents.unit.test.ts +251 -0
  146. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  147. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  148. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +19 -9
  149. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  150. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  151. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  152. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  153. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +661 -0
  154. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  155. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +371 -0
  156. package/src/hooks/__tests__/useToast.unit.test.tsx +449 -30
  157. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  158. package/src/hooks/useToast.ts +4 -4
  159. package/src/rbac/audit-enhanced.ts +339 -0
  160. package/src/services/EventService.ts +1 -0
  161. package/src/services/__tests__/AuthService.test.ts +473 -0
  162. package/src/services/__tests__/EventService.test.ts +390 -0
  163. package/src/services/__tests__/InactivityService.test.ts +217 -0
  164. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  165. package/src/styles/core.css +1 -0
  166. package/dist/chunk-2GJ5GL77.js.map +0 -1
  167. package/dist/chunk-2LM4QQGH.js.map +0 -1
  168. package/dist/chunk-KA3PSVNV.js.map +0 -1
  169. package/dist/chunk-UKZWNQMB.js.map +0 -1
  170. package/src/components/DataTable/utils/debugTools.ts +0 -609
  171. package/src/rbac/testing/index.tsx +0 -340
  172. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-DGZDJUYM.js.map} +0 -0
  173. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  174. /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
  175. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  176. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  177. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  178. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  179. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  180. /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  181. /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
+