@digitaldefiance/express-suite-react-components 5.0.0 → 5.0.2

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 (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -0
  3. package/package.json +3 -4
  4. package/src/auth/AdminRoute.tsx +54 -0
  5. package/src/auth/Private.tsx +17 -0
  6. package/src/auth/PrivateRoute.tsx +28 -0
  7. package/src/auth/UnAuth.tsx +16 -0
  8. package/src/auth/UnAuthRoute.tsx +30 -0
  9. package/src/auth/{index.d.ts → index.ts} +2 -3
  10. package/src/components/ApiAccess.tsx +175 -0
  11. package/src/components/BackupCodeLoginForm.tsx +489 -0
  12. package/src/components/BackupCodesForm.tsx +287 -0
  13. package/src/components/CategoryMenu.tsx +155 -0
  14. package/src/components/ChangePasswordForm.tsx +273 -0
  15. package/src/components/ConfirmationDialog.tsx +48 -0
  16. package/src/components/CurrencyCodeSelector.tsx +60 -0
  17. package/src/components/CurrencyInput.tsx +80 -0
  18. package/src/components/DashboardPage.tsx +24 -0
  19. package/src/components/DropdownMenu.tsx +105 -0
  20. package/src/components/ExpirationSecondsSelector.tsx +60 -0
  21. package/src/components/FakeEmailAdminPanel.tsx +374 -0
  22. package/src/components/Flag.tsx +52 -0
  23. package/src/components/ForgotPasswordForm.tsx +174 -0
  24. package/src/components/LoginForm.tsx +468 -0
  25. package/src/components/LogoutPage.tsx +21 -0
  26. package/src/components/RegisterForm.tsx +1169 -0
  27. package/src/components/ResetPasswordForm.tsx +247 -0
  28. package/src/components/SideMenu.tsx +46 -0
  29. package/src/components/SideMenuListItem.tsx +75 -0
  30. package/src/components/T.tsx +24 -0
  31. package/src/components/TDiv.tsx +26 -0
  32. package/src/components/TImgAlt.tsx +28 -0
  33. package/src/components/TopMenu.tsx +252 -0
  34. package/src/components/TotpSetupForm.tsx +188 -0
  35. package/src/components/TotpVerificationForm.tsx +148 -0
  36. package/src/components/TranslatedTitle.tsx +31 -0
  37. package/src/components/UserLanguageSelector.tsx +45 -0
  38. package/src/components/UserMenu.tsx +15 -0
  39. package/src/components/UserSettingsForm.tsx +904 -0
  40. package/src/components/VerifyEmailPage.tsx +222 -0
  41. package/src/components/__tests__/RegisterForm.totp.spec.tsx +377 -0
  42. package/src/components/__tests__/TotpSetupForm.spec.tsx +258 -0
  43. package/src/components/__tests__/TotpVerificationForm.spec.tsx +259 -0
  44. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +396 -0
  45. package/src/components/{index.d.ts → index.ts} +1 -1
  46. package/src/contexts/AuthProvider.spec.tsx +1284 -0
  47. package/src/contexts/AuthProvider.tsx +977 -0
  48. package/src/contexts/I18nProvider.tsx +164 -0
  49. package/src/contexts/MenuContext.tsx +426 -0
  50. package/src/contexts/SuiteConfigProvider.tsx +93 -0
  51. package/src/contexts/ThemeProvider.tsx +67 -0
  52. package/src/contexts/{index.d.ts → index.ts} +0 -1
  53. package/src/hooks/{index.d.ts → index.ts} +0 -1
  54. package/src/hooks/useAuthenticatedApi.tsx +67 -0
  55. package/src/hooks/useBackupCodes.ts +105 -0
  56. package/src/hooks/useEmailVerification.ts +54 -0
  57. package/src/hooks/useExpiringValue.ts +78 -0
  58. package/src/hooks/useLocalStorage.ts +18 -0
  59. package/src/hooks/useUserSettings.ts +271 -0
  60. package/src/{index.d.ts → index.ts} +1 -1
  61. package/src/interfaces/IAppConfig.ts +7 -0
  62. package/src/interfaces/ICategoryConfig.ts +41 -0
  63. package/src/interfaces/IMenuConfig.ts +28 -0
  64. package/src/interfaces/IMenuOption.ts +79 -0
  65. package/src/interfaces/{index.d.ts → index.ts} +0 -1
  66. package/src/services/__mocks__/authService.ts +14 -0
  67. package/src/services/__tests__/authService.totp.property.spec.ts +360 -0
  68. package/src/services/__tests__/authService.totp.spec.ts +439 -0
  69. package/src/services/api.ts +13 -0
  70. package/src/services/authService.ts +692 -0
  71. package/src/services/authenticatedApi.ts +17 -0
  72. package/src/services/index.ts +3 -0
  73. package/src/types/MenuType.ts +15 -0
  74. package/src/types/expirationSeconds.ts +18 -0
  75. package/src/types/index.ts +1 -0
  76. package/src/types/translation.ts +20 -0
  77. package/src/wrappers/BackupCodeLoginWrapper.tsx +34 -0
  78. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  79. package/src/wrappers/ChangePasswordFormWrapper.tsx +34 -0
  80. package/src/wrappers/ForgotPasswordFormWrapper.tsx +45 -0
  81. package/src/wrappers/LoginFormWrapper.tsx +108 -0
  82. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  83. package/src/wrappers/RegisterFormWrapper.tsx +109 -0
  84. package/src/wrappers/ResetPasswordFormWrapper.tsx +57 -0
  85. package/src/wrappers/UserSettingsFormWrapper.tsx +140 -0
  86. package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
  87. package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +339 -0
  88. package/src/wrappers/{index.d.ts → index.tsx} +10 -1
  89. package/src/auth/AdminRoute.d.ts +0 -8
  90. package/src/auth/AdminRoute.d.ts.map +0 -1
  91. package/src/auth/AdminRoute.js +0 -28
  92. package/src/auth/Private.d.ts +0 -6
  93. package/src/auth/Private.d.ts.map +0 -1
  94. package/src/auth/Private.js +0 -14
  95. package/src/auth/PrivateRoute.d.ts +0 -8
  96. package/src/auth/PrivateRoute.d.ts.map +0 -1
  97. package/src/auth/PrivateRoute.js +0 -23
  98. package/src/auth/UnAuth.d.ts +0 -6
  99. package/src/auth/UnAuth.d.ts.map +0 -1
  100. package/src/auth/UnAuth.js +0 -14
  101. package/src/auth/UnAuthRoute.d.ts +0 -8
  102. package/src/auth/UnAuthRoute.d.ts.map +0 -1
  103. package/src/auth/UnAuthRoute.js +0 -22
  104. package/src/auth/index.d.ts.map +0 -1
  105. package/src/auth/index.js +0 -12
  106. package/src/components/ApiAccess.d.ts +0 -16
  107. package/src/components/ApiAccess.d.ts.map +0 -1
  108. package/src/components/ApiAccess.js +0 -77
  109. package/src/components/BackupCodeLoginForm.d.ts +0 -43
  110. package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
  111. package/src/components/BackupCodeLoginForm.js +0 -139
  112. package/src/components/BackupCodesForm.d.ts +0 -26
  113. package/src/components/BackupCodesForm.d.ts.map +0 -1
  114. package/src/components/BackupCodesForm.js +0 -120
  115. package/src/components/CategoryMenu.d.ts +0 -17
  116. package/src/components/CategoryMenu.d.ts.map +0 -1
  117. package/src/components/CategoryMenu.js +0 -65
  118. package/src/components/ChangePasswordForm.d.ts +0 -26
  119. package/src/components/ChangePasswordForm.d.ts.map +0 -1
  120. package/src/components/ChangePasswordForm.js +0 -78
  121. package/src/components/ConfirmationDialog.d.ts +0 -13
  122. package/src/components/ConfirmationDialog.d.ts.map +0 -1
  123. package/src/components/ConfirmationDialog.js +0 -10
  124. package/src/components/CurrencyCodeSelector.d.ts +0 -9
  125. package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
  126. package/src/components/CurrencyCodeSelector.js +0 -31
  127. package/src/components/CurrencyInput.d.ts +0 -13
  128. package/src/components/CurrencyInput.d.ts.map +0 -1
  129. package/src/components/CurrencyInput.js +0 -22
  130. package/src/components/DashboardPage.d.ts +0 -8
  131. package/src/components/DashboardPage.d.ts.map +0 -1
  132. package/src/components/DashboardPage.js +0 -10
  133. package/src/components/DropdownMenu.d.ts +0 -13
  134. package/src/components/DropdownMenu.d.ts.map +0 -1
  135. package/src/components/DropdownMenu.js +0 -63
  136. package/src/components/ExpirationSecondsSelector.d.ts +0 -13
  137. package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
  138. package/src/components/ExpirationSecondsSelector.js +0 -32
  139. package/src/components/FakeEmailAdminPanel.d.ts +0 -23
  140. package/src/components/FakeEmailAdminPanel.d.ts.map +0 -1
  141. package/src/components/FakeEmailAdminPanel.js +0 -147
  142. package/src/components/Flag.d.ts +0 -20
  143. package/src/components/Flag.d.ts.map +0 -1
  144. package/src/components/Flag.js +0 -43
  145. package/src/components/ForgotPasswordForm.d.ts +0 -18
  146. package/src/components/ForgotPasswordForm.d.ts.map +0 -1
  147. package/src/components/ForgotPasswordForm.js +0 -61
  148. package/src/components/LoginForm.d.ts +0 -44
  149. package/src/components/LoginForm.d.ts.map +0 -1
  150. package/src/components/LoginForm.js +0 -123
  151. package/src/components/LogoutPage.d.ts +0 -8
  152. package/src/components/LogoutPage.d.ts.map +0 -1
  153. package/src/components/LogoutPage.js +0 -16
  154. package/src/components/RegisterForm.d.ts +0 -100
  155. package/src/components/RegisterForm.d.ts.map +0 -1
  156. package/src/components/RegisterForm.js +0 -306
  157. package/src/components/ResetPasswordForm.d.ts +0 -23
  158. package/src/components/ResetPasswordForm.d.ts.map +0 -1
  159. package/src/components/ResetPasswordForm.js +0 -78
  160. package/src/components/SideMenu.d.ts +0 -8
  161. package/src/components/SideMenu.d.ts.map +0 -1
  162. package/src/components/SideMenu.js +0 -25
  163. package/src/components/SideMenuListItem.d.ts +0 -13
  164. package/src/components/SideMenuListItem.d.ts.map +0 -1
  165. package/src/components/SideMenuListItem.js +0 -44
  166. package/src/components/T.d.ts +0 -7
  167. package/src/components/T.d.ts.map +0 -1
  168. package/src/components/T.js +0 -13
  169. package/src/components/TDiv.d.ts +0 -9
  170. package/src/components/TDiv.d.ts.map +0 -1
  171. package/src/components/TDiv.js +0 -14
  172. package/src/components/TImgAlt.d.ts +0 -10
  173. package/src/components/TImgAlt.d.ts.map +0 -1
  174. package/src/components/TImgAlt.js +0 -14
  175. package/src/components/TopMenu.d.ts +0 -50
  176. package/src/components/TopMenu.d.ts.map +0 -1
  177. package/src/components/TopMenu.js +0 -79
  178. package/src/components/TotpSetupForm.d.ts +0 -24
  179. package/src/components/TotpSetupForm.d.ts.map +0 -1
  180. package/src/components/TotpSetupForm.js +0 -82
  181. package/src/components/TotpVerificationForm.d.ts +0 -23
  182. package/src/components/TotpVerificationForm.d.ts.map +0 -1
  183. package/src/components/TotpVerificationForm.js +0 -66
  184. package/src/components/TranslatedTitle.d.ts +0 -8
  185. package/src/components/TranslatedTitle.d.ts.map +0 -1
  186. package/src/components/TranslatedTitle.js +0 -15
  187. package/src/components/UserLanguageSelector.d.ts +0 -4
  188. package/src/components/UserLanguageSelector.d.ts.map +0 -1
  189. package/src/components/UserLanguageSelector.js +0 -31
  190. package/src/components/UserMenu.d.ts +0 -4
  191. package/src/components/UserMenu.d.ts.map +0 -1
  192. package/src/components/UserMenu.js +0 -12
  193. package/src/components/UserSettingsForm.d.ts +0 -113
  194. package/src/components/UserSettingsForm.d.ts.map +0 -1
  195. package/src/components/UserSettingsForm.js +0 -269
  196. package/src/components/VerifyEmailPage.d.ts +0 -24
  197. package/src/components/VerifyEmailPage.d.ts.map +0 -1
  198. package/src/components/VerifyEmailPage.js +0 -81
  199. package/src/components/index.d.ts.map +0 -1
  200. package/src/components/index.js +0 -35
  201. package/src/contexts/AuthProvider.d.ts +0 -186
  202. package/src/contexts/AuthProvider.d.ts.map +0 -1
  203. package/src/contexts/AuthProvider.js +0 -515
  204. package/src/contexts/I18nProvider.d.ts +0 -27
  205. package/src/contexts/I18nProvider.d.ts.map +0 -1
  206. package/src/contexts/I18nProvider.js +0 -60
  207. package/src/contexts/MenuContext.d.ts +0 -20
  208. package/src/contexts/MenuContext.d.ts.map +0 -1
  209. package/src/contexts/MenuContext.js +0 -275
  210. package/src/contexts/SuiteConfigProvider.d.ts +0 -44
  211. package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
  212. package/src/contexts/SuiteConfigProvider.js +0 -43
  213. package/src/contexts/ThemeProvider.d.ts +0 -15
  214. package/src/contexts/ThemeProvider.d.ts.map +0 -1
  215. package/src/contexts/ThemeProvider.js +0 -36
  216. package/src/contexts/index.d.ts.map +0 -1
  217. package/src/contexts/index.js +0 -8
  218. package/src/hooks/index.d.ts.map +0 -1
  219. package/src/hooks/index.js +0 -9
  220. package/src/hooks/useAuthenticatedApi.d.ts +0 -34
  221. package/src/hooks/useAuthenticatedApi.d.ts.map +0 -1
  222. package/src/hooks/useAuthenticatedApi.js +0 -46
  223. package/src/hooks/useBackupCodes.d.ts +0 -15
  224. package/src/hooks/useBackupCodes.d.ts.map +0 -1
  225. package/src/hooks/useBackupCodes.js +0 -74
  226. package/src/hooks/useEmailVerification.d.ts +0 -10
  227. package/src/hooks/useEmailVerification.d.ts.map +0 -1
  228. package/src/hooks/useEmailVerification.js +0 -40
  229. package/src/hooks/useExpiringValue.d.ts +0 -14
  230. package/src/hooks/useExpiringValue.d.ts.map +0 -1
  231. package/src/hooks/useExpiringValue.js +0 -53
  232. package/src/hooks/useLocalStorage.d.ts +0 -2
  233. package/src/hooks/useLocalStorage.d.ts.map +0 -1
  234. package/src/hooks/useLocalStorage.js +0 -15
  235. package/src/hooks/useUserSettings.d.ts +0 -48
  236. package/src/hooks/useUserSettings.d.ts.map +0 -1
  237. package/src/hooks/useUserSettings.js +0 -171
  238. package/src/index.d.ts.map +0 -1
  239. package/src/index.js +0 -12
  240. package/src/interfaces/IAppConfig.d.ts +0 -8
  241. package/src/interfaces/IAppConfig.d.ts.map +0 -1
  242. package/src/interfaces/IAppConfig.js +0 -2
  243. package/src/interfaces/ICategoryConfig.d.ts +0 -41
  244. package/src/interfaces/ICategoryConfig.d.ts.map +0 -1
  245. package/src/interfaces/ICategoryConfig.js +0 -2
  246. package/src/interfaces/IMenuConfig.d.ts +0 -28
  247. package/src/interfaces/IMenuConfig.d.ts.map +0 -1
  248. package/src/interfaces/IMenuConfig.js +0 -2
  249. package/src/interfaces/IMenuOption.d.ts +0 -82
  250. package/src/interfaces/IMenuOption.d.ts.map +0 -1
  251. package/src/interfaces/IMenuOption.js +0 -2
  252. package/src/interfaces/index.d.ts.map +0 -1
  253. package/src/interfaces/index.js +0 -7
  254. package/src/services/__mocks__/authService.d.ts +0 -21
  255. package/src/services/__mocks__/authService.d.ts.map +0 -1
  256. package/src/services/__mocks__/authService.js +0 -15
  257. package/src/services/api.d.ts +0 -3
  258. package/src/services/api.d.ts.map +0 -1
  259. package/src/services/api.js +0 -14
  260. package/src/services/authService.d.ts +0 -126
  261. package/src/services/authService.d.ts.map +0 -1
  262. package/src/services/authService.js +0 -471
  263. package/src/services/authenticatedApi.d.ts +0 -3
  264. package/src/services/authenticatedApi.d.ts.map +0 -1
  265. package/src/services/authenticatedApi.js +0 -18
  266. package/src/services/index.d.ts +0 -4
  267. package/src/services/index.d.ts.map +0 -1
  268. package/src/services/index.js +0 -6
  269. package/src/types/MenuType.d.ts +0 -11
  270. package/src/types/MenuType.d.ts.map +0 -1
  271. package/src/types/MenuType.js +0 -12
  272. package/src/types/expirationSeconds.d.ts +0 -3
  273. package/src/types/expirationSeconds.d.ts.map +0 -1
  274. package/src/types/expirationSeconds.js +0 -17
  275. package/src/types/index.d.ts +0 -2
  276. package/src/types/index.d.ts.map +0 -1
  277. package/src/types/index.js +0 -4
  278. package/src/types/translation.d.ts +0 -10
  279. package/src/types/translation.d.ts.map +0 -1
  280. package/src/types/translation.js +0 -9
  281. package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
  282. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
  283. package/src/wrappers/BackupCodeLoginWrapper.js +0 -20
  284. package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
  285. package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
  286. package/src/wrappers/BackupCodesWrapper.js +0 -17
  287. package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
  288. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
  289. package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
  290. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +0 -8
  291. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +0 -1
  292. package/src/wrappers/ForgotPasswordFormWrapper.js +0 -29
  293. package/src/wrappers/LoginFormWrapper.d.ts +0 -21
  294. package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
  295. package/src/wrappers/LoginFormWrapper.js +0 -72
  296. package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
  297. package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
  298. package/src/wrappers/LogoutPageWrapper.js +0 -21
  299. package/src/wrappers/RegisterFormWrapper.d.ts +0 -20
  300. package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
  301. package/src/wrappers/RegisterFormWrapper.js +0 -51
  302. package/src/wrappers/ResetPasswordFormWrapper.d.ts +0 -9
  303. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +0 -1
  304. package/src/wrappers/ResetPasswordFormWrapper.js +0 -34
  305. package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -29
  306. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
  307. package/src/wrappers/UserSettingsFormWrapper.js +0 -95
  308. package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
  309. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
  310. package/src/wrappers/VerifyEmailPageWrapper.js +0 -21
  311. package/src/wrappers/index.d.ts.map +0 -1
  312. package/src/wrappers/index.js +0 -24
@@ -0,0 +1,1284 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ jest,
8
+ } from '@jest/globals';
9
+ import { ReactNode } from 'react';
10
+
11
+ // Mock PasswordLoginService
12
+ const mockPasswordLoginService = {
13
+ getWalletAndMnemonicFromLocalStorageBundle: jest.fn() as jest.Mock,
14
+ setupPasswordLoginLocalStorageBundle: jest.fn() as jest.Mock,
15
+ };
16
+
17
+ const mockNavigate = jest.fn();
18
+
19
+ // Create shared mock instance
20
+ const mockAuthService = {
21
+ verifyToken: jest.fn() as jest.Mock,
22
+ directLogin: jest.fn() as jest.Mock,
23
+ emailChallengeLogin: jest.fn() as jest.Mock,
24
+ refreshToken: jest.fn() as jest.Mock,
25
+ register: jest.fn() as jest.Mock,
26
+ requestEmailLogin: jest.fn() as jest.Mock,
27
+ backupCodeLogin: jest.fn() as jest.Mock,
28
+ changePassword: jest.fn() as jest.Mock,
29
+ };
30
+
31
+ // Mock dependencies - must be before imports
32
+ jest.mock('../services/authService', () => ({
33
+ mockAuthService,
34
+ createAuthService: jest.fn(() => mockAuthService),
35
+ }));
36
+ jest.mock('../services/authenticatedApi', () => ({
37
+ createAuthenticatedApiClient: jest.fn(() => ({
38
+ post: jest.fn(),
39
+ get: jest.fn(),
40
+ })),
41
+ }));
42
+
43
+ jest.mock('@digitaldefiance/i18n-lib', () => {
44
+ const actual = jest.requireActual('@digitaldefiance/i18n-lib');
45
+ const mockI18nEngineInstance = {
46
+ t: jest.fn((key: string) => key),
47
+ translate: jest.fn((componentId: string, key: string) => key),
48
+ setLanguage: jest.fn(),
49
+ };
50
+ return {
51
+ ...actual,
52
+ DefaultCurrencyCode: 'USD',
53
+ CurrencyCode: class MockCurrencyCode {
54
+ constructor(public value: string = 'USD') {}
55
+ },
56
+ I18nEngine: {
57
+ getInstance: () => mockI18nEngineInstance,
58
+ },
59
+ ECIESService: jest.fn(),
60
+ t: jest.fn((key) => key),
61
+ };
62
+ });
63
+
64
+ jest.mock('@digitaldefiance/ecies-lib', () => {
65
+ const actual = jest.requireActual('@digitaldefiance/ecies-lib');
66
+
67
+ // Mock ECIESService to avoid ID provider validation issues in tests
68
+ const mockECIESService = jest.fn().mockImplementation(() => ({
69
+ generateNewMnemonic: jest.fn(),
70
+ mnemonicToSimpleKeyPair: jest.fn(),
71
+ getPublicKey: jest.fn(),
72
+ encryptSimpleOrSingle: jest.fn(),
73
+ decryptSimpleOrSingleWithHeader: jest.fn(),
74
+ signMessage: jest.fn(),
75
+ verifyMessage: jest.fn(),
76
+ config: {},
77
+ constants: actual.Constants,
78
+ idProvider: actual.Constants.idProvider,
79
+ }));
80
+
81
+ return {
82
+ ...actual,
83
+ ECIESService: mockECIESService,
84
+ PasswordLoginService: Object.assign(
85
+ jest.fn().mockImplementation(() => mockPasswordLoginService),
86
+ {
87
+ privateKeyStorageKey: 'encryptedPrivateKey',
88
+ saltStorageKey: 'passwordLoginSalt',
89
+ encryptedMnemonicStorageKey: 'encryptedMnemonic',
90
+ profileStorageKey: 'pbkdf2Profile',
91
+ }
92
+ ),
93
+ };
94
+ });
95
+
96
+ jest.mock('./I18nProvider', () => {
97
+ const React = require('react');
98
+ const I18nProvider = ({ children }: { children: any }) => {
99
+ return React.createElement(React.Fragment, null, children);
100
+ };
101
+ I18nProvider.displayName = 'MockI18nProvider';
102
+
103
+ return {
104
+ I18nProvider,
105
+ useI18n: () => ({
106
+ t: (key: string) => key,
107
+ tComponent: (componentId: string, key: string) => key,
108
+ changeLanguage: jest.fn(),
109
+ currentLanguage: 'en-US',
110
+ }),
111
+ };
112
+ });
113
+
114
+ jest.mock('@ethereumjs/wallet', () => ({
115
+ Wallet: {
116
+ generate: jest.fn(() => ({
117
+ getPrivateKey: jest.fn(() => Buffer.from('test')),
118
+ getPublicKey: jest.fn(() => Buffer.from('test')),
119
+ getAddress: jest.fn(() => Buffer.from('test')),
120
+ })),
121
+ },
122
+ }));
123
+
124
+ // Now import after mocks
125
+ import {
126
+ ECIES as ECIESConstants,
127
+ SecureString,
128
+ } from '@digitaldefiance/ecies-lib';
129
+ import { Constants } from '@digitaldefiance/suite-core-lib';
130
+ import { Wallet } from '@ethereumjs/wallet';
131
+ import { act, renderHook, waitFor } from '@testing-library/react';
132
+ import { localStorageMock } from '../../tests/setup';
133
+ import { AuthProvider, useAuth } from './AuthProvider';
134
+ import { I18nProvider } from './I18nProvider';
135
+ import { AppThemeProvider } from './ThemeProvider';
136
+
137
+ // Mock localStorage is imported from test-setup
138
+
139
+ // Mock console methods
140
+ const consoleMock = {
141
+ error: jest.fn(),
142
+ log: jest.fn(),
143
+ };
144
+ Object.defineProperty(console, 'error', { value: consoleMock.error });
145
+
146
+ // Test wrapper component
147
+ const TestWrapper = ({ children }: { children: ReactNode }) => (
148
+ <I18nProvider i18nEngine={null as any} onLanguageChange={async () => {}}>
149
+ <AppThemeProvider>
150
+ <AuthProvider
151
+ baseUrl="http://localhost:3000"
152
+ constants={Constants}
153
+ eciesConfig={ECIESConstants}
154
+ onLogout={mockNavigate}
155
+ >
156
+ {children}
157
+ </AuthProvider>
158
+ </AppThemeProvider>
159
+ </I18nProvider>
160
+ );
161
+
162
+ // Helper to create mock user
163
+ const createMockUser = (admin = false) => ({
164
+ id: '1',
165
+ username: admin ? 'admin' : 'testuser',
166
+ email: admin ? 'admin@example.com' : 'test@example.com',
167
+ roles: [
168
+ {
169
+ id: 'role1',
170
+ _id: 'role1',
171
+ name: admin ? 'admin' : 'user',
172
+ member: true,
173
+ child: false,
174
+ system: false,
175
+ admin,
176
+ createdAt: '2023-01-01T00:00:00.000Z',
177
+ updatedAt: '2023-01-01T00:00:00.000Z',
178
+ deletedAt: undefined,
179
+ createdBy: 'system',
180
+ updatedBy: 'system',
181
+ },
182
+ ],
183
+ siteLanguage: 'en-US',
184
+ timezone: 'UTC',
185
+ currency: 'USD',
186
+ darkMode: false,
187
+ directChallenge: false,
188
+ emailVerified: true,
189
+ });
190
+
191
+ describe('AuthProvider', () => {
192
+ beforeEach(() => {
193
+ localStorageMock.getItem.mockReturnValue(null);
194
+ localStorageMock.setItem.mockClear();
195
+ localStorageMock.removeItem.mockClear();
196
+ mockPasswordLoginService.getWalletAndMnemonicFromLocalStorageBundle.mockClear();
197
+ mockPasswordLoginService.setupPasswordLoginLocalStorageBundle.mockClear();
198
+ mockAuthService.verifyToken.mockClear();
199
+ mockAuthService.directLogin.mockClear();
200
+ mockAuthService.emailChallengeLogin.mockClear();
201
+ mockAuthService.refreshToken.mockClear();
202
+ mockAuthService.register.mockClear();
203
+ mockAuthService.requestEmailLogin.mockClear();
204
+ mockAuthService.backupCodeLogin.mockClear();
205
+ });
206
+
207
+ afterEach(() => {
208
+ jest.clearAllTimers();
209
+ });
210
+
211
+ describe('Initial State', () => {
212
+ it('should initialize with default values', async () => {
213
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
214
+
215
+ await waitFor(() => {
216
+ expect(result.current.isAuthenticated).toBe(false);
217
+ expect(result.current.user).toBe(null);
218
+ expect(result.current.userData).toBe(null);
219
+ expect(result.current.token).toBe(null);
220
+ expect(result.current.mnemonic).toBeUndefined();
221
+ expect(result.current.wallet).toBeUndefined();
222
+ expect(result.current.admin).toBe(false);
223
+ });
224
+ });
225
+
226
+ it('should initialize expiration seconds from localStorage', async () => {
227
+ localStorageMock.getItem.mockImplementation((key) => {
228
+ if (key === 'mnemonicExpirationSeconds') return '300';
229
+ if (key === 'walletExpirationSeconds') return '600';
230
+ return null;
231
+ });
232
+
233
+ renderHook(() => useAuth(), { wrapper: TestWrapper });
234
+
235
+ await waitFor(() => {
236
+ expect(localStorageMock.getItem).toHaveBeenCalledWith(
237
+ 'mnemonicExpirationSeconds'
238
+ );
239
+ expect(localStorageMock.getItem).toHaveBeenCalledWith(
240
+ 'walletExpirationSeconds'
241
+ );
242
+ });
243
+ });
244
+ });
245
+
246
+ describe('checkAuth', () => {
247
+ it('should clear auth state when no token exists', async () => {
248
+ localStorageMock.getItem.mockReturnValue(null);
249
+
250
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
251
+
252
+ await waitFor(() => {
253
+ expect(result.current.loading).toBe(false);
254
+ expect(result.current.isCheckingAuth).toBe(false);
255
+ expect(result.current.isAuthenticated).toBe(false);
256
+ });
257
+ });
258
+
259
+ it('should verify token and set user when token exists', async () => {
260
+ const mockUser = createMockUser();
261
+ mockAuthService.verifyToken.mockResolvedValue(mockUser);
262
+
263
+ localStorageMock.getItem.mockImplementation((key) =>
264
+ key === 'authToken' ? 'valid-token' : null
265
+ );
266
+
267
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
268
+
269
+ await waitFor(
270
+ () => {
271
+ expect(result.current.loading).toBe(false);
272
+ expect(result.current.isCheckingAuth).toBe(false);
273
+ expect(result.current.isAuthenticated).toBe(true);
274
+ },
275
+ { timeout: 5000 }
276
+ );
277
+
278
+ expect(result.current.userData).toEqual(mockUser);
279
+ expect(result.current.token).toBe('valid-token');
280
+ });
281
+
282
+ it('should handle token verification failure', async () => {
283
+ localStorageMock.getItem.mockImplementation((key) => {
284
+ if (key === 'authToken') return 'invalid-token';
285
+ return null;
286
+ });
287
+ mockAuthService.verifyToken.mockRejectedValue(new Error('Token invalid'));
288
+
289
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
290
+
291
+ await waitFor(() => {
292
+ expect(result.current.isAuthenticated).toBe(false);
293
+ expect(result.current.userData).toBe(null);
294
+ expect(result.current.loading).toBe(false);
295
+ expect(result.current.isCheckingAuth).toBe(false);
296
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
297
+ });
298
+ });
299
+
300
+ it('should set admin flag for admin users', async () => {
301
+ const mockAdminUser = createMockUser(true);
302
+ mockAuthService.verifyToken.mockResolvedValue(mockAdminUser);
303
+
304
+ localStorageMock.getItem.mockImplementation((key) =>
305
+ key === 'authToken' ? 'admin-token' : null
306
+ );
307
+
308
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
309
+
310
+ await waitFor(
311
+ () => {
312
+ expect(result.current.loading).toBe(false);
313
+ expect(result.current.admin).toBe(true);
314
+ },
315
+ { timeout: 5000 }
316
+ );
317
+ });
318
+
319
+ it('should propagate displayName from verifyToken into user state', async () => {
320
+ const mockUser = { ...createMockUser(), displayName: 'Verified User' };
321
+ mockAuthService.verifyToken.mockResolvedValue(mockUser);
322
+
323
+ localStorageMock.getItem.mockImplementation((key) =>
324
+ key === 'authToken' ? 'valid-token' : null
325
+ );
326
+
327
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
328
+
329
+ await waitFor(
330
+ () => {
331
+ expect(result.current.loading).toBe(false);
332
+ expect(result.current.isAuthenticated).toBe(true);
333
+ },
334
+ { timeout: 5000 }
335
+ );
336
+
337
+ expect(result.current.userData).toEqual(
338
+ expect.objectContaining({ displayName: 'Verified User' })
339
+ );
340
+ });
341
+ });
342
+
343
+ describe('directLogin', () => {
344
+ it('should perform successful direct login', async () => {
345
+ const mockMnemonic = new SecureString('test mnemonic');
346
+ const mockWallet = Wallet.generate();
347
+ const mockUser = createMockUser();
348
+ const mockLoginResult = {
349
+ token: 'new-token',
350
+ user: mockUser,
351
+ wallet: mockWallet,
352
+ message: 'Login successful',
353
+ };
354
+
355
+ mockAuthService.directLogin.mockResolvedValue(mockLoginResult);
356
+
357
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
358
+
359
+ await waitFor(() => {
360
+ expect(result.current.directLogin).toBeDefined();
361
+ });
362
+
363
+ let loginResult;
364
+ await act(async () => {
365
+ loginResult = await result.current.directLogin(
366
+ mockMnemonic,
367
+ 'testuser'
368
+ );
369
+ });
370
+
371
+ expect(mockAuthService.directLogin).toHaveBeenCalled();
372
+ expect(loginResult).toEqual(mockLoginResult);
373
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
374
+ 'authToken',
375
+ 'new-token'
376
+ );
377
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
378
+ 'user',
379
+ JSON.stringify(mockUser)
380
+ );
381
+ });
382
+
383
+ it('should perform successful direct login with custom expiration seconds', async () => {
384
+ const mockMnemonic = new SecureString('test mnemonic');
385
+ const mockWallet = Wallet.generate();
386
+ const mockUser = createMockUser();
387
+ const mockLoginResult = {
388
+ token: 'new-token',
389
+ user: mockUser,
390
+ wallet: mockWallet,
391
+ message: 'Login successful',
392
+ };
393
+
394
+ mockAuthService.directLogin.mockResolvedValue(mockLoginResult);
395
+
396
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
397
+
398
+ let loginResult;
399
+ await act(async () => {
400
+ loginResult = await result.current.directLogin(
401
+ mockMnemonic,
402
+ 'testuser',
403
+ undefined,
404
+ 600, // mnemonicExpirationSeconds
405
+ 1200 // walletExpirationSeconds
406
+ );
407
+ });
408
+
409
+ expect(loginResult).toEqual(mockLoginResult);
410
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
411
+ 'authToken',
412
+ 'new-token'
413
+ );
414
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
415
+ 'user',
416
+ JSON.stringify(mockUser)
417
+ );
418
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
419
+ 'mnemonicExpirationSeconds',
420
+ '600'
421
+ );
422
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
423
+ 'walletExpirationSeconds',
424
+ '1200'
425
+ );
426
+ });
427
+
428
+ it('should handle direct login failure', async () => {
429
+ const mockMnemonic = new SecureString('test mnemonic');
430
+ const mockError = { error: 'Login failed' };
431
+
432
+ mockAuthService.directLogin.mockResolvedValue(mockError);
433
+
434
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
435
+
436
+ await act(async () => {
437
+ await result.current.directLogin(mockMnemonic, 'testuser');
438
+ });
439
+ expect(result.current.isAuthenticated).toBe(false);
440
+ });
441
+
442
+ it('should propagate displayName from directLogin into user state', async () => {
443
+ const mockMnemonic = new SecureString('test mnemonic');
444
+ const mockWallet = Wallet.generate();
445
+ const mockUser = { ...createMockUser(), displayName: 'Direct User' };
446
+ const mockLoginResult = {
447
+ token: 'new-token',
448
+ user: mockUser,
449
+ wallet: mockWallet,
450
+ message: 'Login successful',
451
+ };
452
+
453
+ mockAuthService.directLogin.mockResolvedValue(mockLoginResult);
454
+
455
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
456
+
457
+ await waitFor(() => {
458
+ expect(result.current.directLogin).toBeDefined();
459
+ });
460
+
461
+ await act(async () => {
462
+ await result.current.directLogin(mockMnemonic, 'testuser');
463
+ });
464
+
465
+ expect(result.current.userData).toEqual(
466
+ expect.objectContaining({ displayName: 'Direct User' })
467
+ );
468
+ });
469
+ });
470
+
471
+ describe('emailChallengeLogin', () => {
472
+ it('should perform successful email challenge login', async () => {
473
+ const mockMnemonic = new SecureString('test mnemonic');
474
+ const mockWallet = Wallet.generate();
475
+ const mockUser = createMockUser();
476
+ const mockLoginResult = {
477
+ token: 'new-token',
478
+ user: mockUser,
479
+ wallet: mockWallet,
480
+ message: 'Login successful',
481
+ };
482
+
483
+ mockAuthService.emailChallengeLogin.mockResolvedValue(mockLoginResult);
484
+
485
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
486
+
487
+ let loginResult;
488
+ await act(async () => {
489
+ loginResult = await result.current.emailChallengeLogin(
490
+ mockMnemonic,
491
+ 'challenge-token',
492
+ 'testuser'
493
+ );
494
+ });
495
+
496
+ expect(loginResult).toEqual(mockLoginResult);
497
+ });
498
+
499
+ it('should perform successful email challenge login with custom expiration seconds', async () => {
500
+ const mockMnemonic = new SecureString('test mnemonic');
501
+ const mockWallet = Wallet.generate();
502
+ const mockUser = createMockUser();
503
+ const mockLoginResult = {
504
+ token: 'new-token',
505
+ user: mockUser,
506
+ wallet: mockWallet,
507
+ message: 'Login successful',
508
+ };
509
+
510
+ mockAuthService.emailChallengeLogin.mockResolvedValue(mockLoginResult);
511
+
512
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
513
+
514
+ let loginResult;
515
+ await act(async () => {
516
+ loginResult = await result.current.emailChallengeLogin(
517
+ mockMnemonic,
518
+ 'challenge-token',
519
+ 'testuser',
520
+ undefined,
521
+ 300, // mnemonicExpirationSeconds
522
+ 900 // walletExpirationSeconds
523
+ );
524
+ });
525
+
526
+ expect(loginResult).toEqual(mockLoginResult);
527
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
528
+ 'mnemonicExpirationSeconds',
529
+ '300'
530
+ );
531
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
532
+ 'walletExpirationSeconds',
533
+ '900'
534
+ );
535
+ });
536
+ });
537
+
538
+ describe('refreshToken', () => {
539
+ it('should refresh token successfully', async () => {
540
+ const mockUser = createMockUser();
541
+ const mockRefreshResult = {
542
+ token: 'refreshed-token',
543
+ user: mockUser,
544
+ };
545
+
546
+ mockAuthService.refreshToken.mockResolvedValue(mockRefreshResult);
547
+
548
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
549
+
550
+ await waitFor(() => {
551
+ expect(result.current.refreshToken).toBeDefined();
552
+ });
553
+
554
+ let refreshResult;
555
+ await act(async () => {
556
+ refreshResult = await result.current.refreshToken();
557
+ });
558
+
559
+ expect(refreshResult).toEqual(mockRefreshResult);
560
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
561
+ 'authToken',
562
+ 'refreshed-token'
563
+ );
564
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
565
+ 'user',
566
+ JSON.stringify(mockUser)
567
+ );
568
+ });
569
+
570
+ it('should handle refresh token failure', async () => {
571
+ mockAuthService.refreshToken.mockRejectedValue(
572
+ new Error('Refresh failed')
573
+ );
574
+
575
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
576
+
577
+ await waitFor(() => {
578
+ expect(result.current.refreshToken).toBeDefined();
579
+ });
580
+
581
+ await expect(
582
+ act(async () => {
583
+ await result.current.refreshToken();
584
+ })
585
+ ).rejects.toThrow('Refresh failed');
586
+
587
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
588
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith('user');
589
+ });
590
+ });
591
+
592
+ describe('register', () => {
593
+ it('should register user successfully', async () => {
594
+ const mockRegisterResult = {
595
+ success: true,
596
+ message: 'Registration successful',
597
+ mnemonic: 'test mnemonic',
598
+ };
599
+
600
+ mockAuthService.register.mockResolvedValue(mockRegisterResult);
601
+
602
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
603
+
604
+ await act(async () => {
605
+ await result.current.register(
606
+ 'testuser',
607
+ 'test@example.com',
608
+ 'UTC',
609
+ 'password'
610
+ );
611
+ });
612
+ });
613
+
614
+ it('should pass displayName to authService.register', async () => {
615
+ const mockRegisterResult = {
616
+ success: true,
617
+ message: 'Registration successful',
618
+ mnemonic: 'test mnemonic',
619
+ };
620
+
621
+ mockAuthService.register.mockResolvedValue(mockRegisterResult);
622
+
623
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
624
+
625
+ await act(async () => {
626
+ await result.current.register(
627
+ 'testuser',
628
+ 'test@example.com',
629
+ 'UTC',
630
+ 'password',
631
+ undefined,
632
+ 'My Display Name'
633
+ );
634
+ });
635
+
636
+ expect(mockAuthService.register).toHaveBeenCalledWith(
637
+ 'testuser',
638
+ 'test@example.com',
639
+ 'UTC',
640
+ 'password',
641
+ undefined,
642
+ 'My Display Name',
643
+ undefined
644
+ );
645
+ });
646
+ });
647
+
648
+ describe('requestEmailLogin', () => {
649
+ it('should request email login successfully', async () => {
650
+ const mockMessage = 'Email sent successfully';
651
+ mockAuthService.requestEmailLogin.mockResolvedValue(mockMessage);
652
+
653
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
654
+
655
+ await act(async () => {
656
+ await result.current.requestEmailLogin('testuser');
657
+ });
658
+ });
659
+ });
660
+
661
+ describe('backupCodeLogin', () => {
662
+ it('should perform backup code login successfully', async () => {
663
+ const mockUser = createMockUser();
664
+ const mockLoginResult = {
665
+ token: 'backup-token',
666
+ user: mockUser,
667
+ codeCount: 5,
668
+ mnemonic: 'recovered mnemonic',
669
+ message: 'Login successful',
670
+ };
671
+
672
+ mockAuthService.backupCodeLogin.mockResolvedValue(mockLoginResult);
673
+
674
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
675
+
676
+ let loginResult;
677
+ await act(async () => {
678
+ loginResult = await result.current.backupCodeLogin(
679
+ 'testuser',
680
+ 'backup-code',
681
+ false,
682
+ true
683
+ );
684
+ });
685
+
686
+ expect(loginResult).toEqual({
687
+ token: 'backup-token',
688
+ codeCount: 5,
689
+ mnemonic: 'recovered mnemonic',
690
+ message: 'Login successful',
691
+ });
692
+ });
693
+ });
694
+
695
+ describe('logout', () => {
696
+ it('should logout user and clear all data', async () => {
697
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
698
+
699
+ await act(async () => {
700
+ await result.current.logout();
701
+ });
702
+
703
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith('user');
704
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
705
+ expect(result.current.isAuthenticated).toBe(false);
706
+ expect(result.current.userData).toBe(null);
707
+ expect(result.current.mnemonic).toBeUndefined();
708
+ expect(result.current.wallet).toBeUndefined();
709
+ expect(mockNavigate).toHaveBeenCalled();
710
+ });
711
+ });
712
+
713
+ describe('verifyToken', () => {
714
+ it('should verify token successfully', async () => {
715
+ const mockUser = createMockUser();
716
+ mockAuthService.verifyToken.mockResolvedValue(mockUser);
717
+
718
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
719
+
720
+ let verifyResult;
721
+ await act(async () => {
722
+ verifyResult = await result.current.verifyToken('test-token');
723
+ });
724
+
725
+ expect(verifyResult).toBe(true);
726
+ expect(mockAuthService.verifyToken).toHaveBeenCalledWith('test-token');
727
+ });
728
+
729
+ it('should handle token verification failure', async () => {
730
+ mockAuthService.verifyToken.mockResolvedValue({ error: 'Invalid token' });
731
+
732
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
733
+
734
+ let verifyResult;
735
+ await act(async () => {
736
+ verifyResult = await result.current.verifyToken('invalid-token');
737
+ });
738
+
739
+ expect(verifyResult).toBe(false);
740
+ expect(mockAuthService.verifyToken).toHaveBeenCalledWith('invalid-token');
741
+ });
742
+ });
743
+
744
+ describe('changePassword', () => {
745
+ it('should change password successfully', async () => {
746
+ // Mock localStorage to have encrypted password (password login available)
747
+ localStorageMock.getItem.mockImplementation((key) => {
748
+ if (key === 'encryptedPassword') return 'mock-encrypted-password';
749
+ return null;
750
+ });
751
+
752
+ // Mock successful password login service operations
753
+ const mockMnemonic = new SecureString('test mnemonic');
754
+ const mockWallet = Wallet.generate();
755
+ mockPasswordLoginService.getWalletAndMnemonicFromLocalStorageBundle.mockResolvedValue(
756
+ {
757
+ mnemonic: mockMnemonic,
758
+ wallet: mockWallet,
759
+ }
760
+ );
761
+ mockPasswordLoginService.setupPasswordLoginLocalStorageBundle.mockResolvedValue(
762
+ mockWallet
763
+ );
764
+
765
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
766
+
767
+ await act(async () => {
768
+ await result.current.changePassword('oldpass', 'newpass');
769
+ });
770
+ });
771
+
772
+ it('should handle password change failure', async () => {
773
+ // Mock localStorage to not have encrypted password (password login not available)
774
+ localStorageMock.getItem.mockReturnValue(null);
775
+
776
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
777
+
778
+ let changeResult;
779
+ await act(async () => {
780
+ changeResult = await result.current.changePassword(
781
+ 'oldpass',
782
+ 'newpass'
783
+ );
784
+ });
785
+
786
+ expect(changeResult).toEqual({
787
+ error: 'error_login_passwordLoginNotSetup',
788
+ errorType: 'PasswordLoginNotSetup',
789
+ });
790
+ });
791
+ });
792
+
793
+ describe('Mnemonic Management', () => {
794
+ it('should set and clear mnemonic', async () => {
795
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
796
+ const mockMnemonic = new SecureString('test mnemonic');
797
+
798
+ await waitFor(() => {
799
+ expect(result.current.setMnemonic).toBeDefined();
800
+ });
801
+
802
+ act(() => {
803
+ result.current.setMnemonic(mockMnemonic, 10);
804
+ });
805
+
806
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
807
+
808
+ act(() => {
809
+ result.current.clearMnemonic();
810
+ });
811
+
812
+ expect(result.current.mnemonic).toBeUndefined();
813
+ });
814
+
815
+ it('should store mnemonic expiration seconds to localStorage when provided', async () => {
816
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
817
+ const mockMnemonic = new SecureString('test mnemonic');
818
+
819
+ await waitFor(() => {
820
+ expect(result.current.setMnemonic).toBeDefined();
821
+ });
822
+
823
+ act(() => {
824
+ result.current.setMnemonic(mockMnemonic, 300);
825
+ });
826
+
827
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
828
+ 'mnemonicExpirationSeconds',
829
+ '300'
830
+ );
831
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
832
+ });
833
+
834
+ it('should use stored expiration seconds when duration not provided', async () => {
835
+ localStorageMock.getItem.mockImplementation((key) =>
836
+ key === 'mnemonicExpirationSeconds' ? '600' : null
837
+ );
838
+
839
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
840
+ const mockMnemonic = new SecureString('test mnemonic');
841
+
842
+ await waitFor(() => {
843
+ expect(result.current.setMnemonic).toBeDefined();
844
+ });
845
+
846
+ act(() => {
847
+ result.current.setMnemonic(mockMnemonic);
848
+ });
849
+
850
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
851
+ // Should not call setItem when no duration provided
852
+ expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
853
+ 'mnemonicExpirationSeconds',
854
+ expect.any(String)
855
+ );
856
+ });
857
+ });
858
+
859
+ describe('Wallet Management', () => {
860
+ it('should set and clear wallet', async () => {
861
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
862
+ const mockWallet = Wallet.generate();
863
+
864
+ await waitFor(() => {
865
+ expect(result.current.setWallet).toBeDefined();
866
+ });
867
+
868
+ act(() => {
869
+ result.current.setWallet(mockWallet, 10);
870
+ });
871
+
872
+ expect(result.current.wallet).toEqual(mockWallet);
873
+
874
+ act(() => {
875
+ result.current.clearWallet();
876
+ });
877
+
878
+ expect(result.current.wallet).toBeUndefined();
879
+ });
880
+
881
+ it('should store wallet expiration seconds to localStorage when provided', async () => {
882
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
883
+ const mockWallet = Wallet.generate();
884
+
885
+ await waitFor(() => {
886
+ expect(result.current.setWallet).toBeDefined();
887
+ });
888
+
889
+ act(() => {
890
+ result.current.setWallet(mockWallet, 900);
891
+ });
892
+
893
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
894
+ 'walletExpirationSeconds',
895
+ '900'
896
+ );
897
+ expect(result.current.wallet).toEqual(mockWallet);
898
+ });
899
+
900
+ it('should use stored expiration seconds when duration not provided', async () => {
901
+ localStorageMock.getItem.mockImplementation((key) =>
902
+ key === 'walletExpirationSeconds' ? '1200' : null
903
+ );
904
+
905
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
906
+ const mockWallet = Wallet.generate();
907
+
908
+ await waitFor(() => {
909
+ expect(result.current.setWallet).toBeDefined();
910
+ });
911
+
912
+ act(() => {
913
+ result.current.setWallet(mockWallet);
914
+ });
915
+
916
+ expect(result.current.wallet).toEqual(mockWallet);
917
+ // Should not call setItem when no duration provided
918
+ expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
919
+ 'walletExpirationSeconds',
920
+ expect.any(String)
921
+ );
922
+ });
923
+ });
924
+
925
+ describe('Expiration Settings Management', () => {
926
+ it('should set mnemonic expiration seconds and update localStorage', async () => {
927
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
928
+
929
+ await waitFor(() => {
930
+ expect(result.current.setMnemonicExpirationSeconds).toBeDefined();
931
+ });
932
+
933
+ act(() => {
934
+ result.current.setMnemonicExpirationSeconds(1800);
935
+ });
936
+
937
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
938
+ 'mnemonicExpirationSeconds',
939
+ '1800'
940
+ );
941
+ });
942
+
943
+ it('should set wallet expiration seconds and update localStorage', async () => {
944
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
945
+
946
+ await waitFor(() => {
947
+ expect(result.current.setWalletExpirationSeconds).toBeDefined();
948
+ });
949
+
950
+ act(() => {
951
+ result.current.setWalletExpirationSeconds(3600);
952
+ });
953
+
954
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
955
+ 'walletExpirationSeconds',
956
+ '3600'
957
+ );
958
+ });
959
+
960
+ it('should use updated expiration seconds after setting them', async () => {
961
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
962
+ const mockMnemonic = new SecureString('test mnemonic');
963
+ const mockWallet = Wallet.generate();
964
+
965
+ await waitFor(() => {
966
+ expect(result.current.setMnemonicExpirationSeconds).toBeDefined();
967
+ expect(result.current.setWalletExpirationSeconds).toBeDefined();
968
+ });
969
+
970
+ // Set new expiration times
971
+ act(() => {
972
+ result.current.setMnemonicExpirationSeconds(2400);
973
+ result.current.setWalletExpirationSeconds(4800);
974
+ });
975
+
976
+ // Clear the setItem calls from setting expiration times
977
+ localStorageMock.setItem.mockClear();
978
+
979
+ // Now set mnemonic and wallet without duration - should use the stored values
980
+ act(() => {
981
+ result.current.setMnemonic(mockMnemonic);
982
+ });
983
+
984
+ act(() => {
985
+ result.current.setWallet(mockWallet);
986
+ });
987
+
988
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
989
+ expect(result.current.wallet).toEqual(mockWallet);
990
+ // Should not call setItem again since we're using stored defaults
991
+ expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
992
+ 'mnemonicExpirationSeconds',
993
+ expect.any(String)
994
+ );
995
+ expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
996
+ 'walletExpirationSeconds',
997
+ expect.any(String)
998
+ );
999
+ });
1000
+ });
1001
+
1002
+ describe('Timeout Management', () => {
1003
+ it('should set expiration times and use them for timeouts', async () => {
1004
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1005
+ const mockMnemonic = new SecureString('test mnemonic');
1006
+ const mockWallet = Wallet.generate();
1007
+
1008
+ await waitFor(() => {
1009
+ expect(result.current.setMnemonicExpirationSeconds).toBeDefined();
1010
+ expect(result.current.setWalletExpirationSeconds).toBeDefined();
1011
+ });
1012
+
1013
+ // Set custom expiration times
1014
+ act(() => {
1015
+ result.current.setMnemonicExpirationSeconds(120); // 2 minutes
1016
+ result.current.setWalletExpirationSeconds(180); // 3 minutes
1017
+ });
1018
+
1019
+ // Verify localStorage was updated
1020
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
1021
+ 'mnemonicExpirationSeconds',
1022
+ '120'
1023
+ );
1024
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
1025
+ 'walletExpirationSeconds',
1026
+ '180'
1027
+ );
1028
+
1029
+ // Set mnemonic and wallet without explicit duration - should use the custom times
1030
+ act(() => {
1031
+ result.current.setMnemonic(mockMnemonic);
1032
+ result.current.setWallet(mockWallet);
1033
+ });
1034
+
1035
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
1036
+ expect(result.current.wallet).toEqual(mockWallet);
1037
+
1038
+ // Verify timeout functions are set up (they should be active but we won't test timing)
1039
+ expect(result.current.mnemonic).toBeDefined();
1040
+ expect(result.current.wallet).toBeDefined();
1041
+ });
1042
+ });
1043
+
1044
+ describe('passwordLogin', () => {
1045
+ it('should perform successful password login', async () => {
1046
+ const mockMnemonic = new SecureString('test mnemonic');
1047
+ const mockWallet = Wallet.generate();
1048
+ const mockUser = createMockUser();
1049
+ const mockLoginResult = {
1050
+ token: 'password-token',
1051
+ message: 'Login successful',
1052
+ user: mockUser,
1053
+ wallet: mockWallet,
1054
+ };
1055
+
1056
+ localStorageMock.getItem.mockImplementation((key) => {
1057
+ if (key === 'passwordLoginSalt') return '0'.repeat(64);
1058
+ if (key === 'encryptedPrivateKey') return '0'.repeat(128);
1059
+ if (key === 'encryptedMnemonic') return '0'.repeat(128);
1060
+ if (key === 'pbkdf2Profile') return 'BROWSER_PASSWORD';
1061
+ return null;
1062
+ });
1063
+
1064
+ mockPasswordLoginService.getWalletAndMnemonicFromLocalStorageBundle.mockResolvedValue(
1065
+ {
1066
+ mnemonic: mockMnemonic,
1067
+ wallet: mockWallet,
1068
+ }
1069
+ );
1070
+ mockAuthService.directLogin.mockResolvedValue(mockLoginResult);
1071
+
1072
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1073
+
1074
+ let loginResult;
1075
+ await act(async () => {
1076
+ loginResult = await result.current.passwordLogin(
1077
+ new SecureString('password')
1078
+ );
1079
+ });
1080
+
1081
+ expect(loginResult).toEqual(mockLoginResult);
1082
+ expect(
1083
+ mockPasswordLoginService.getWalletAndMnemonicFromLocalStorageBundle
1084
+ ).toHaveBeenCalled();
1085
+ });
1086
+
1087
+ it('should handle password login when not available', async () => {
1088
+ localStorageMock.getItem.mockReturnValue(null);
1089
+
1090
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1091
+
1092
+ let loginResult;
1093
+ await act(async () => {
1094
+ loginResult = await result.current.passwordLogin(
1095
+ new SecureString('password')
1096
+ );
1097
+ });
1098
+
1099
+ expect(loginResult).toEqual({
1100
+ error: 'error_login_passwordLoginNotSetup',
1101
+ errorType: 'PasswordLoginNotSetup',
1102
+ });
1103
+ });
1104
+ });
1105
+
1106
+ describe('setUpBrowserPasswordLogin', () => {
1107
+ it('should set up password login successfully', async () => {
1108
+ const mockMnemonic = new SecureString('test mnemonic');
1109
+ const mockWallet = Wallet.generate();
1110
+
1111
+ mockPasswordLoginService.setupPasswordLoginLocalStorageBundle.mockResolvedValue(
1112
+ mockWallet
1113
+ );
1114
+
1115
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1116
+
1117
+ let setupResult;
1118
+ await act(async () => {
1119
+ setupResult = await result.current.setUpBrowserPasswordLogin(
1120
+ mockMnemonic,
1121
+ new SecureString('password')
1122
+ );
1123
+ });
1124
+
1125
+ expect(setupResult).toEqual({
1126
+ success: true,
1127
+ message: expect.any(String),
1128
+ });
1129
+ expect(
1130
+ mockPasswordLoginService.setupPasswordLoginLocalStorageBundle
1131
+ ).toHaveBeenCalled();
1132
+ });
1133
+
1134
+ it('should handle password setup failure', async () => {
1135
+ const mockMnemonic = new SecureString('test mnemonic');
1136
+ mockPasswordLoginService.setupPasswordLoginLocalStorageBundle.mockRejectedValue(
1137
+ new Error('Setup failed')
1138
+ );
1139
+
1140
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1141
+
1142
+ let setupResult;
1143
+ await act(async () => {
1144
+ setupResult = await result.current.setUpBrowserPasswordLogin(
1145
+ mockMnemonic,
1146
+ new SecureString('password')
1147
+ );
1148
+ });
1149
+
1150
+ expect(setupResult).toEqual({
1151
+ success: false,
1152
+ message: 'passwordLogin_setup_failure',
1153
+ });
1154
+ });
1155
+ });
1156
+
1157
+ describe('isBrowserPasswordLoginAvailable', () => {
1158
+ it('should return true when encrypted password exists', async () => {
1159
+ localStorageMock.getItem.mockImplementation((key) =>
1160
+ key === 'encryptedPrivateKey' ? 'mock-encrypted-private-key' : null
1161
+ );
1162
+
1163
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1164
+
1165
+ await waitFor(() => {
1166
+ expect(result.current.isBrowserPasswordLoginAvailable?.()).toBe(true);
1167
+ });
1168
+ });
1169
+
1170
+ it('should return false when no encrypted password exists', async () => {
1171
+ localStorageMock.getItem.mockReturnValue(null);
1172
+
1173
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1174
+
1175
+ await waitFor(() => {
1176
+ expect(result.current.isBrowserPasswordLoginAvailable?.()).toBe(false);
1177
+ });
1178
+ });
1179
+ });
1180
+
1181
+ describe('Timeout Setup', () => {
1182
+ it('should set up timeouts when setting mnemonic and wallet', async () => {
1183
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1184
+ const mockMnemonic = new SecureString('test mnemonic');
1185
+ const mockWallet = Wallet.generate();
1186
+
1187
+ await waitFor(() => {
1188
+ expect(result.current.setMnemonic).toBeDefined();
1189
+ expect(result.current.setWallet).toBeDefined();
1190
+ });
1191
+
1192
+ act(() => {
1193
+ result.current.setMnemonic(mockMnemonic, 300);
1194
+ result.current.setWallet(mockWallet, 600);
1195
+ });
1196
+
1197
+ expect(result.current.mnemonic).toEqual(mockMnemonic);
1198
+ expect(result.current.wallet).toEqual(mockWallet);
1199
+ });
1200
+ });
1201
+
1202
+ describe('State Exposure', () => {
1203
+ it('should expose mnemonicExpirationSeconds and walletExpirationSeconds', async () => {
1204
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1205
+
1206
+ await waitFor(() => {
1207
+ expect(typeof result.current.mnemonicExpirationSeconds).toBe('number');
1208
+ expect(typeof result.current.walletExpirationSeconds).toBe('number');
1209
+ });
1210
+ });
1211
+ });
1212
+
1213
+ describe('Edge Cases', () => {
1214
+ it('should handle invalid localStorage values gracefully', async () => {
1215
+ localStorageMock.getItem.mockImplementation((key) => {
1216
+ if (key === 'mnemonicExpirationSeconds') return 'invalid';
1217
+ if (key === 'walletExpirationSeconds') return 'invalid';
1218
+ return null;
1219
+ });
1220
+
1221
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1222
+
1223
+ await waitFor(() => {
1224
+ expect(typeof result.current.mnemonicExpirationSeconds).toBe('number');
1225
+ expect(typeof result.current.walletExpirationSeconds).toBe('number');
1226
+ });
1227
+ });
1228
+
1229
+ it('should handle checkAuth with error response from verifyToken', async () => {
1230
+ localStorageMock.getItem.mockImplementation((key) => {
1231
+ if (key === 'authToken') return 'token-with-error';
1232
+ return null;
1233
+ });
1234
+ mockAuthService.verifyToken.mockResolvedValue({ error: 'Token expired' });
1235
+
1236
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1237
+
1238
+ await waitFor(() => {
1239
+ expect(result.current.isAuthenticated).toBe(false);
1240
+ expect(result.current.token).toBe(null);
1241
+ });
1242
+ });
1243
+
1244
+ it('should handle changePassword with invalid current password', async () => {
1245
+ localStorageMock.getItem.mockImplementation((key) => {
1246
+ if (key === 'encryptedPassword') return 'mock-encrypted-password';
1247
+ return null;
1248
+ });
1249
+
1250
+ mockPasswordLoginService.getWalletAndMnemonicFromLocalStorageBundle.mockResolvedValue(
1251
+ {
1252
+ mnemonic: null, // Invalid mnemonic
1253
+ wallet: Wallet.generate(),
1254
+ }
1255
+ );
1256
+
1257
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1258
+
1259
+ await act(async () => {
1260
+ await result.current.changePassword('wrongpass', 'newpass');
1261
+ });
1262
+ });
1263
+ });
1264
+
1265
+ describe('Error Handling', () => {
1266
+ it('should handle console errors gracefully', async () => {
1267
+ localStorageMock.getItem.mockImplementation((key) => {
1268
+ if (key === 'authToken') return 'invalid-token';
1269
+ return null;
1270
+ });
1271
+ mockAuthService.verifyToken.mockRejectedValue(new Error('Network error'));
1272
+
1273
+ const { result } = renderHook(() => useAuth(), { wrapper: TestWrapper });
1274
+
1275
+ await waitFor(() => {
1276
+ expect(result.current.isAuthenticated).toBe(false);
1277
+ expect(consoleMock.error).toHaveBeenCalledWith(
1278
+ 'Token verification failed:',
1279
+ expect.any(Error)
1280
+ );
1281
+ });
1282
+ });
1283
+ });
1284
+ });