@digitaldefiance/express-suite-react-components 5.0.1 → 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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +3 -4
  3. package/src/auth/AdminRoute.tsx +54 -0
  4. package/src/auth/Private.tsx +17 -0
  5. package/src/auth/PrivateRoute.tsx +28 -0
  6. package/src/auth/UnAuth.tsx +16 -0
  7. package/src/auth/UnAuthRoute.tsx +30 -0
  8. package/src/auth/{index.d.ts → index.ts} +2 -3
  9. package/src/components/ApiAccess.tsx +175 -0
  10. package/src/components/BackupCodeLoginForm.tsx +489 -0
  11. package/src/components/BackupCodesForm.tsx +287 -0
  12. package/src/components/CategoryMenu.tsx +155 -0
  13. package/src/components/ChangePasswordForm.tsx +273 -0
  14. package/src/components/ConfirmationDialog.tsx +48 -0
  15. package/src/components/CurrencyCodeSelector.tsx +60 -0
  16. package/src/components/CurrencyInput.tsx +80 -0
  17. package/src/components/DashboardPage.tsx +24 -0
  18. package/src/components/DropdownMenu.tsx +105 -0
  19. package/src/components/ExpirationSecondsSelector.tsx +60 -0
  20. package/src/components/FakeEmailAdminPanel.tsx +374 -0
  21. package/src/components/Flag.tsx +52 -0
  22. package/src/components/ForgotPasswordForm.tsx +174 -0
  23. package/src/components/LoginForm.tsx +468 -0
  24. package/src/components/LogoutPage.tsx +21 -0
  25. package/src/components/RegisterForm.tsx +1169 -0
  26. package/src/components/ResetPasswordForm.tsx +247 -0
  27. package/src/components/SideMenu.tsx +46 -0
  28. package/src/components/SideMenuListItem.tsx +75 -0
  29. package/src/components/T.tsx +24 -0
  30. package/src/components/TDiv.tsx +26 -0
  31. package/src/components/TImgAlt.tsx +28 -0
  32. package/src/components/TopMenu.tsx +252 -0
  33. package/src/components/TotpSetupForm.tsx +188 -0
  34. package/src/components/TotpVerificationForm.tsx +148 -0
  35. package/src/components/TranslatedTitle.tsx +31 -0
  36. package/src/components/UserLanguageSelector.tsx +45 -0
  37. package/src/components/UserMenu.tsx +15 -0
  38. package/src/components/UserSettingsForm.tsx +904 -0
  39. package/src/components/VerifyEmailPage.tsx +222 -0
  40. package/src/components/__tests__/RegisterForm.totp.spec.tsx +377 -0
  41. package/src/components/__tests__/TotpSetupForm.spec.tsx +258 -0
  42. package/src/components/__tests__/TotpVerificationForm.spec.tsx +259 -0
  43. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +396 -0
  44. package/src/components/{index.d.ts → index.ts} +1 -1
  45. package/src/contexts/AuthProvider.spec.tsx +1284 -0
  46. package/src/contexts/AuthProvider.tsx +977 -0
  47. package/src/contexts/I18nProvider.tsx +164 -0
  48. package/src/contexts/MenuContext.tsx +426 -0
  49. package/src/contexts/SuiteConfigProvider.tsx +93 -0
  50. package/src/contexts/ThemeProvider.tsx +67 -0
  51. package/src/contexts/{index.d.ts → index.ts} +0 -1
  52. package/src/hooks/{index.d.ts → index.ts} +0 -1
  53. package/src/hooks/useAuthenticatedApi.tsx +67 -0
  54. package/src/hooks/useBackupCodes.ts +105 -0
  55. package/src/hooks/useEmailVerification.ts +54 -0
  56. package/src/hooks/useExpiringValue.ts +78 -0
  57. package/src/hooks/useLocalStorage.ts +18 -0
  58. package/src/hooks/useUserSettings.ts +271 -0
  59. package/src/{index.d.ts → index.ts} +1 -1
  60. package/src/interfaces/IAppConfig.ts +7 -0
  61. package/src/interfaces/ICategoryConfig.ts +41 -0
  62. package/src/interfaces/IMenuConfig.ts +28 -0
  63. package/src/interfaces/IMenuOption.ts +79 -0
  64. package/src/interfaces/{index.d.ts → index.ts} +0 -1
  65. package/src/services/__mocks__/authService.ts +14 -0
  66. package/src/services/__tests__/authService.totp.property.spec.ts +360 -0
  67. package/src/services/__tests__/authService.totp.spec.ts +439 -0
  68. package/src/services/api.ts +13 -0
  69. package/src/services/authService.ts +692 -0
  70. package/src/services/authenticatedApi.ts +17 -0
  71. package/src/services/index.ts +3 -0
  72. package/src/types/MenuType.ts +15 -0
  73. package/src/types/expirationSeconds.ts +18 -0
  74. package/src/types/index.ts +1 -0
  75. package/src/types/translation.ts +20 -0
  76. package/src/wrappers/BackupCodeLoginWrapper.tsx +34 -0
  77. package/src/wrappers/BackupCodesWrapper.tsx +28 -0
  78. package/src/wrappers/ChangePasswordFormWrapper.tsx +34 -0
  79. package/src/wrappers/ForgotPasswordFormWrapper.tsx +45 -0
  80. package/src/wrappers/LoginFormWrapper.tsx +108 -0
  81. package/src/wrappers/LogoutPageWrapper.tsx +30 -0
  82. package/src/wrappers/RegisterFormWrapper.tsx +109 -0
  83. package/src/wrappers/ResetPasswordFormWrapper.tsx +57 -0
  84. package/src/wrappers/UserSettingsFormWrapper.tsx +140 -0
  85. package/src/wrappers/VerifyEmailPageWrapper.tsx +27 -0
  86. package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +339 -0
  87. package/src/wrappers/{index.d.ts → index.tsx} +10 -1
  88. package/src/auth/AdminRoute.d.ts +0 -8
  89. package/src/auth/AdminRoute.d.ts.map +0 -1
  90. package/src/auth/AdminRoute.js +0 -28
  91. package/src/auth/Private.d.ts +0 -6
  92. package/src/auth/Private.d.ts.map +0 -1
  93. package/src/auth/Private.js +0 -14
  94. package/src/auth/PrivateRoute.d.ts +0 -8
  95. package/src/auth/PrivateRoute.d.ts.map +0 -1
  96. package/src/auth/PrivateRoute.js +0 -23
  97. package/src/auth/UnAuth.d.ts +0 -6
  98. package/src/auth/UnAuth.d.ts.map +0 -1
  99. package/src/auth/UnAuth.js +0 -14
  100. package/src/auth/UnAuthRoute.d.ts +0 -8
  101. package/src/auth/UnAuthRoute.d.ts.map +0 -1
  102. package/src/auth/UnAuthRoute.js +0 -22
  103. package/src/auth/index.d.ts.map +0 -1
  104. package/src/auth/index.js +0 -12
  105. package/src/components/ApiAccess.d.ts +0 -16
  106. package/src/components/ApiAccess.d.ts.map +0 -1
  107. package/src/components/ApiAccess.js +0 -77
  108. package/src/components/BackupCodeLoginForm.d.ts +0 -43
  109. package/src/components/BackupCodeLoginForm.d.ts.map +0 -1
  110. package/src/components/BackupCodeLoginForm.js +0 -139
  111. package/src/components/BackupCodesForm.d.ts +0 -26
  112. package/src/components/BackupCodesForm.d.ts.map +0 -1
  113. package/src/components/BackupCodesForm.js +0 -120
  114. package/src/components/CategoryMenu.d.ts +0 -17
  115. package/src/components/CategoryMenu.d.ts.map +0 -1
  116. package/src/components/CategoryMenu.js +0 -65
  117. package/src/components/ChangePasswordForm.d.ts +0 -26
  118. package/src/components/ChangePasswordForm.d.ts.map +0 -1
  119. package/src/components/ChangePasswordForm.js +0 -78
  120. package/src/components/ConfirmationDialog.d.ts +0 -13
  121. package/src/components/ConfirmationDialog.d.ts.map +0 -1
  122. package/src/components/ConfirmationDialog.js +0 -10
  123. package/src/components/CurrencyCodeSelector.d.ts +0 -9
  124. package/src/components/CurrencyCodeSelector.d.ts.map +0 -1
  125. package/src/components/CurrencyCodeSelector.js +0 -31
  126. package/src/components/CurrencyInput.d.ts +0 -13
  127. package/src/components/CurrencyInput.d.ts.map +0 -1
  128. package/src/components/CurrencyInput.js +0 -22
  129. package/src/components/DashboardPage.d.ts +0 -8
  130. package/src/components/DashboardPage.d.ts.map +0 -1
  131. package/src/components/DashboardPage.js +0 -10
  132. package/src/components/DropdownMenu.d.ts +0 -13
  133. package/src/components/DropdownMenu.d.ts.map +0 -1
  134. package/src/components/DropdownMenu.js +0 -63
  135. package/src/components/ExpirationSecondsSelector.d.ts +0 -13
  136. package/src/components/ExpirationSecondsSelector.d.ts.map +0 -1
  137. package/src/components/ExpirationSecondsSelector.js +0 -32
  138. package/src/components/FakeEmailAdminPanel.d.ts +0 -23
  139. package/src/components/FakeEmailAdminPanel.d.ts.map +0 -1
  140. package/src/components/FakeEmailAdminPanel.js +0 -147
  141. package/src/components/Flag.d.ts +0 -20
  142. package/src/components/Flag.d.ts.map +0 -1
  143. package/src/components/Flag.js +0 -43
  144. package/src/components/ForgotPasswordForm.d.ts +0 -18
  145. package/src/components/ForgotPasswordForm.d.ts.map +0 -1
  146. package/src/components/ForgotPasswordForm.js +0 -61
  147. package/src/components/LoginForm.d.ts +0 -44
  148. package/src/components/LoginForm.d.ts.map +0 -1
  149. package/src/components/LoginForm.js +0 -123
  150. package/src/components/LogoutPage.d.ts +0 -8
  151. package/src/components/LogoutPage.d.ts.map +0 -1
  152. package/src/components/LogoutPage.js +0 -16
  153. package/src/components/RegisterForm.d.ts +0 -100
  154. package/src/components/RegisterForm.d.ts.map +0 -1
  155. package/src/components/RegisterForm.js +0 -306
  156. package/src/components/ResetPasswordForm.d.ts +0 -23
  157. package/src/components/ResetPasswordForm.d.ts.map +0 -1
  158. package/src/components/ResetPasswordForm.js +0 -78
  159. package/src/components/SideMenu.d.ts +0 -8
  160. package/src/components/SideMenu.d.ts.map +0 -1
  161. package/src/components/SideMenu.js +0 -25
  162. package/src/components/SideMenuListItem.d.ts +0 -13
  163. package/src/components/SideMenuListItem.d.ts.map +0 -1
  164. package/src/components/SideMenuListItem.js +0 -44
  165. package/src/components/T.d.ts +0 -7
  166. package/src/components/T.d.ts.map +0 -1
  167. package/src/components/T.js +0 -13
  168. package/src/components/TDiv.d.ts +0 -9
  169. package/src/components/TDiv.d.ts.map +0 -1
  170. package/src/components/TDiv.js +0 -14
  171. package/src/components/TImgAlt.d.ts +0 -10
  172. package/src/components/TImgAlt.d.ts.map +0 -1
  173. package/src/components/TImgAlt.js +0 -14
  174. package/src/components/TopMenu.d.ts +0 -50
  175. package/src/components/TopMenu.d.ts.map +0 -1
  176. package/src/components/TopMenu.js +0 -79
  177. package/src/components/TotpSetupForm.d.ts +0 -24
  178. package/src/components/TotpSetupForm.d.ts.map +0 -1
  179. package/src/components/TotpSetupForm.js +0 -82
  180. package/src/components/TotpVerificationForm.d.ts +0 -23
  181. package/src/components/TotpVerificationForm.d.ts.map +0 -1
  182. package/src/components/TotpVerificationForm.js +0 -66
  183. package/src/components/TranslatedTitle.d.ts +0 -8
  184. package/src/components/TranslatedTitle.d.ts.map +0 -1
  185. package/src/components/TranslatedTitle.js +0 -15
  186. package/src/components/UserLanguageSelector.d.ts +0 -4
  187. package/src/components/UserLanguageSelector.d.ts.map +0 -1
  188. package/src/components/UserLanguageSelector.js +0 -31
  189. package/src/components/UserMenu.d.ts +0 -4
  190. package/src/components/UserMenu.d.ts.map +0 -1
  191. package/src/components/UserMenu.js +0 -12
  192. package/src/components/UserSettingsForm.d.ts +0 -113
  193. package/src/components/UserSettingsForm.d.ts.map +0 -1
  194. package/src/components/UserSettingsForm.js +0 -269
  195. package/src/components/VerifyEmailPage.d.ts +0 -24
  196. package/src/components/VerifyEmailPage.d.ts.map +0 -1
  197. package/src/components/VerifyEmailPage.js +0 -81
  198. package/src/components/index.d.ts.map +0 -1
  199. package/src/components/index.js +0 -35
  200. package/src/contexts/AuthProvider.d.ts +0 -186
  201. package/src/contexts/AuthProvider.d.ts.map +0 -1
  202. package/src/contexts/AuthProvider.js +0 -515
  203. package/src/contexts/I18nProvider.d.ts +0 -27
  204. package/src/contexts/I18nProvider.d.ts.map +0 -1
  205. package/src/contexts/I18nProvider.js +0 -60
  206. package/src/contexts/MenuContext.d.ts +0 -20
  207. package/src/contexts/MenuContext.d.ts.map +0 -1
  208. package/src/contexts/MenuContext.js +0 -275
  209. package/src/contexts/SuiteConfigProvider.d.ts +0 -44
  210. package/src/contexts/SuiteConfigProvider.d.ts.map +0 -1
  211. package/src/contexts/SuiteConfigProvider.js +0 -43
  212. package/src/contexts/ThemeProvider.d.ts +0 -15
  213. package/src/contexts/ThemeProvider.d.ts.map +0 -1
  214. package/src/contexts/ThemeProvider.js +0 -36
  215. package/src/contexts/index.d.ts.map +0 -1
  216. package/src/contexts/index.js +0 -8
  217. package/src/hooks/index.d.ts.map +0 -1
  218. package/src/hooks/index.js +0 -9
  219. package/src/hooks/useAuthenticatedApi.d.ts +0 -34
  220. package/src/hooks/useAuthenticatedApi.d.ts.map +0 -1
  221. package/src/hooks/useAuthenticatedApi.js +0 -46
  222. package/src/hooks/useBackupCodes.d.ts +0 -15
  223. package/src/hooks/useBackupCodes.d.ts.map +0 -1
  224. package/src/hooks/useBackupCodes.js +0 -74
  225. package/src/hooks/useEmailVerification.d.ts +0 -10
  226. package/src/hooks/useEmailVerification.d.ts.map +0 -1
  227. package/src/hooks/useEmailVerification.js +0 -40
  228. package/src/hooks/useExpiringValue.d.ts +0 -14
  229. package/src/hooks/useExpiringValue.d.ts.map +0 -1
  230. package/src/hooks/useExpiringValue.js +0 -53
  231. package/src/hooks/useLocalStorage.d.ts +0 -2
  232. package/src/hooks/useLocalStorage.d.ts.map +0 -1
  233. package/src/hooks/useLocalStorage.js +0 -15
  234. package/src/hooks/useUserSettings.d.ts +0 -48
  235. package/src/hooks/useUserSettings.d.ts.map +0 -1
  236. package/src/hooks/useUserSettings.js +0 -171
  237. package/src/index.d.ts.map +0 -1
  238. package/src/index.js +0 -12
  239. package/src/interfaces/IAppConfig.d.ts +0 -8
  240. package/src/interfaces/IAppConfig.d.ts.map +0 -1
  241. package/src/interfaces/IAppConfig.js +0 -2
  242. package/src/interfaces/ICategoryConfig.d.ts +0 -41
  243. package/src/interfaces/ICategoryConfig.d.ts.map +0 -1
  244. package/src/interfaces/ICategoryConfig.js +0 -2
  245. package/src/interfaces/IMenuConfig.d.ts +0 -28
  246. package/src/interfaces/IMenuConfig.d.ts.map +0 -1
  247. package/src/interfaces/IMenuConfig.js +0 -2
  248. package/src/interfaces/IMenuOption.d.ts +0 -82
  249. package/src/interfaces/IMenuOption.d.ts.map +0 -1
  250. package/src/interfaces/IMenuOption.js +0 -2
  251. package/src/interfaces/index.d.ts.map +0 -1
  252. package/src/interfaces/index.js +0 -7
  253. package/src/services/__mocks__/authService.d.ts +0 -21
  254. package/src/services/__mocks__/authService.d.ts.map +0 -1
  255. package/src/services/__mocks__/authService.js +0 -15
  256. package/src/services/api.d.ts +0 -3
  257. package/src/services/api.d.ts.map +0 -1
  258. package/src/services/api.js +0 -14
  259. package/src/services/authService.d.ts +0 -126
  260. package/src/services/authService.d.ts.map +0 -1
  261. package/src/services/authService.js +0 -471
  262. package/src/services/authenticatedApi.d.ts +0 -3
  263. package/src/services/authenticatedApi.d.ts.map +0 -1
  264. package/src/services/authenticatedApi.js +0 -18
  265. package/src/services/index.d.ts +0 -4
  266. package/src/services/index.d.ts.map +0 -1
  267. package/src/services/index.js +0 -6
  268. package/src/types/MenuType.d.ts +0 -11
  269. package/src/types/MenuType.d.ts.map +0 -1
  270. package/src/types/MenuType.js +0 -12
  271. package/src/types/expirationSeconds.d.ts +0 -3
  272. package/src/types/expirationSeconds.d.ts.map +0 -1
  273. package/src/types/expirationSeconds.js +0 -17
  274. package/src/types/index.d.ts +0 -2
  275. package/src/types/index.d.ts.map +0 -1
  276. package/src/types/index.js +0 -4
  277. package/src/types/translation.d.ts +0 -10
  278. package/src/types/translation.d.ts.map +0 -1
  279. package/src/types/translation.js +0 -9
  280. package/src/wrappers/BackupCodeLoginWrapper.d.ts +0 -8
  281. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +0 -1
  282. package/src/wrappers/BackupCodeLoginWrapper.js +0 -20
  283. package/src/wrappers/BackupCodesWrapper.d.ts +0 -7
  284. package/src/wrappers/BackupCodesWrapper.d.ts.map +0 -1
  285. package/src/wrappers/BackupCodesWrapper.js +0 -17
  286. package/src/wrappers/ChangePasswordFormWrapper.d.ts +0 -8
  287. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +0 -1
  288. package/src/wrappers/ChangePasswordFormWrapper.js +0 -21
  289. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +0 -8
  290. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +0 -1
  291. package/src/wrappers/ForgotPasswordFormWrapper.js +0 -29
  292. package/src/wrappers/LoginFormWrapper.d.ts +0 -21
  293. package/src/wrappers/LoginFormWrapper.d.ts.map +0 -1
  294. package/src/wrappers/LoginFormWrapper.js +0 -72
  295. package/src/wrappers/LogoutPageWrapper.d.ts +0 -9
  296. package/src/wrappers/LogoutPageWrapper.d.ts.map +0 -1
  297. package/src/wrappers/LogoutPageWrapper.js +0 -21
  298. package/src/wrappers/RegisterFormWrapper.d.ts +0 -20
  299. package/src/wrappers/RegisterFormWrapper.d.ts.map +0 -1
  300. package/src/wrappers/RegisterFormWrapper.js +0 -51
  301. package/src/wrappers/ResetPasswordFormWrapper.d.ts +0 -9
  302. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +0 -1
  303. package/src/wrappers/ResetPasswordFormWrapper.js +0 -34
  304. package/src/wrappers/UserSettingsFormWrapper.d.ts +0 -29
  305. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +0 -1
  306. package/src/wrappers/UserSettingsFormWrapper.js +0 -95
  307. package/src/wrappers/VerifyEmailPageWrapper.d.ts +0 -8
  308. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +0 -1
  309. package/src/wrappers/VerifyEmailPageWrapper.js +0 -21
  310. package/src/wrappers/index.d.ts.map +0 -1
  311. package/src/wrappers/index.js +0 -24
@@ -0,0 +1,396 @@
1
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2
+ import { UserSettingsForm, UserSettingsFormProps } from '../UserSettingsForm';
3
+ import { I18nProvider } from '../../contexts';
4
+ import { I18nEngine } from '@digitaldefiance/i18n-lib';
5
+
6
+ // Mock qrcode.react to avoid SVG rendering complexity in jsdom
7
+ jest.mock('qrcode.react', () => ({
8
+ QRCodeSVG: (props: Record<string, unknown>) => (
9
+ <svg
10
+ data-testid="qr-code-svg"
11
+ role={props.role as string}
12
+ aria-label={props['aria-label'] as string}
13
+ />
14
+ ),
15
+ }));
16
+
17
+ const defaultInitialValues = {
18
+ email: 'user@example.com',
19
+ timezone: 'UTC',
20
+ siteLanguage: 'en',
21
+ currency: 'USD',
22
+ darkMode: false,
23
+ directChallenge: false,
24
+ };
25
+
26
+ const defaultLanguages = [{ code: 'en', label: 'English' }];
27
+
28
+ const baseProps: Pick<UserSettingsFormProps, 'initialValues' | 'onSubmit' | 'languages'> = {
29
+ initialValues: defaultInitialValues,
30
+ onSubmit: jest.fn().mockResolvedValue({ success: true, message: 'Saved' }),
31
+ languages: defaultLanguages,
32
+ };
33
+
34
+ const renderWithI18n = (component: React.ReactElement) => {
35
+ const engine = I18nEngine.getInstance('default');
36
+ return render(<I18nProvider i18nEngine={engine}>{component}</I18nProvider>);
37
+ };
38
+
39
+ describe('UserSettingsForm TOTP integration', () => {
40
+ // Suppress act() warnings from Formik's internal state management
41
+ const originalError = console.error;
42
+ beforeAll(() => {
43
+ console.error = (...args: unknown[]) => {
44
+ if (
45
+ typeof args[0] === 'string' &&
46
+ (args[0].includes('not wrapped in act') ||
47
+ args[0].includes('cannot be a descendant'))
48
+ ) {
49
+ return;
50
+ }
51
+ originalError.call(console, ...args);
52
+ };
53
+ });
54
+
55
+ afterAll(() => {
56
+ console.error = originalError;
57
+ });
58
+
59
+ beforeEach(() => {
60
+ jest.clearAllMocks();
61
+ });
62
+
63
+ /**
64
+ * Validates: Requirement 14.5
65
+ * No TOTP controls when totpEnabled prop is undefined (backward compatible).
66
+ */
67
+ it('does not render TOTP controls when totpEnabled is undefined', () => {
68
+ renderWithI18n(<UserSettingsForm {...baseProps} />);
69
+
70
+ expect(
71
+ screen.queryByText(/two-factor authentication/i)
72
+ ).not.toBeInTheDocument();
73
+ expect(
74
+ screen.queryByTestId('enable-totp-button')
75
+ ).not.toBeInTheDocument();
76
+ expect(
77
+ screen.queryByTestId('disable-totp-button')
78
+ ).not.toBeInTheDocument();
79
+ expect(
80
+ screen.queryByTestId('reset-totp-button')
81
+ ).not.toBeInTheDocument();
82
+ });
83
+
84
+ /**
85
+ * Validates: Requirement 10.1
86
+ * TOTP status is displayed when totpEnabled is provided.
87
+ */
88
+ it('displays TOTP status as disabled when totpEnabled=false', () => {
89
+ renderWithI18n(
90
+ <UserSettingsForm {...baseProps} totpEnabled={false} />
91
+ );
92
+
93
+ expect(
94
+ screen.getByText(/two-factor authentication/i)
95
+ ).toBeInTheDocument();
96
+ expect(
97
+ screen.getByText(/2fa is currently disabled/i)
98
+ ).toBeInTheDocument();
99
+ });
100
+
101
+ /**
102
+ * Validates: Requirement 10.1
103
+ * TOTP status is displayed as enabled when totpEnabled=true.
104
+ */
105
+ it('displays TOTP status as enabled when totpEnabled=true', () => {
106
+ renderWithI18n(
107
+ <UserSettingsForm
108
+ {...baseProps}
109
+ totpEnabled={true}
110
+ onTotpDisable={jest.fn()}
111
+ onTotpReset={jest.fn()}
112
+ />
113
+ );
114
+
115
+ expect(
116
+ screen.getByText(/two-factor authentication/i)
117
+ ).toBeInTheDocument();
118
+ expect(
119
+ screen.getByText(/2fa is currently enabled/i)
120
+ ).toBeInTheDocument();
121
+ });
122
+
123
+ /**
124
+ * Validates: Requirement 10.2
125
+ * "Enable 2FA" button is shown when totpEnabled=false.
126
+ */
127
+ it('renders "Enable 2FA" button when totpEnabled=false', () => {
128
+ renderWithI18n(
129
+ <UserSettingsForm
130
+ {...baseProps}
131
+ totpEnabled={false}
132
+ onTotpSetup={jest.fn()}
133
+ />
134
+ );
135
+
136
+ const enableButton = screen.getByTestId('enable-totp-button');
137
+ expect(enableButton).toBeInTheDocument();
138
+ expect(enableButton).toHaveTextContent(/enable 2fa/i);
139
+ });
140
+
141
+ /**
142
+ * Validates: Requirement 10.3
143
+ * "Disable 2FA" and "Reset 2FA" buttons are shown when totpEnabled=true.
144
+ */
145
+ it('renders "Disable 2FA" and "Reset 2FA" buttons when totpEnabled=true', () => {
146
+ renderWithI18n(
147
+ <UserSettingsForm
148
+ {...baseProps}
149
+ totpEnabled={true}
150
+ onTotpDisable={jest.fn()}
151
+ onTotpReset={jest.fn()}
152
+ />
153
+ );
154
+
155
+ const disableButton = screen.getByTestId('disable-totp-button');
156
+ expect(disableButton).toBeInTheDocument();
157
+ expect(disableButton).toHaveTextContent(/disable 2fa/i);
158
+
159
+ const resetButton = screen.getByTestId('reset-totp-button');
160
+ expect(resetButton).toBeInTheDocument();
161
+ expect(resetButton).toHaveTextContent(/reset 2fa/i);
162
+ });
163
+
164
+ /**
165
+ * Validates: Requirement 10.2
166
+ * Clicking "Enable 2FA" calls onTotpSetup and renders TotpSetupForm inline.
167
+ */
168
+ it('shows TotpSetupForm inline after clicking "Enable 2FA"', async () => {
169
+ const mockOnTotpSetup = jest.fn().mockResolvedValue({
170
+ provisioningUri:
171
+ 'otpauth://totp/TestIssuer:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=TestIssuer',
172
+ secret: 'JBSWY3DPEHPK3PXP',
173
+ });
174
+
175
+ renderWithI18n(
176
+ <UserSettingsForm
177
+ {...baseProps}
178
+ totpEnabled={false}
179
+ onTotpSetup={mockOnTotpSetup}
180
+ onTotpConfirm={jest.fn()}
181
+ />
182
+ );
183
+
184
+ fireEvent.click(screen.getByTestId('enable-totp-button'));
185
+
186
+ await waitFor(() => {
187
+ expect(mockOnTotpSetup).toHaveBeenCalled();
188
+ });
189
+
190
+ // TotpSetupForm should be rendered inline with QR code and secret
191
+ await waitFor(() => {
192
+ expect(screen.getByTestId('qr-code-svg')).toBeInTheDocument();
193
+ });
194
+ expect(screen.getByText('JBSWY3DPEHPK3PXP')).toBeInTheDocument();
195
+ });
196
+
197
+ /**
198
+ * Validates: Requirement 10.6
199
+ * Clicking "Disable 2FA" shows a 6-digit code prompt.
200
+ */
201
+ it('shows code prompt when clicking "Disable 2FA"', () => {
202
+ renderWithI18n(
203
+ <UserSettingsForm
204
+ {...baseProps}
205
+ totpEnabled={true}
206
+ onTotpDisable={jest.fn()}
207
+ onTotpReset={jest.fn()}
208
+ />
209
+ );
210
+
211
+ fireEvent.click(screen.getByTestId('disable-totp-button'));
212
+
213
+ // Code input should appear
214
+ expect(
215
+ screen.getByText(/enter your current 2fa code to disable/i)
216
+ ).toBeInTheDocument();
217
+ expect(
218
+ screen.getByTestId('disable-totp-submit-button')
219
+ ).toBeInTheDocument();
220
+ expect(
221
+ screen.getByTestId('cancel-disable-totp-button')
222
+ ).toBeInTheDocument();
223
+ });
224
+
225
+ /**
226
+ * Validates: Requirement 10.6
227
+ * Disable flow: entering code and submitting calls onTotpDisable.
228
+ */
229
+ it('calls onTotpDisable with the entered code', async () => {
230
+ const mockOnTotpDisable = jest
231
+ .fn()
232
+ .mockResolvedValue({ success: true });
233
+
234
+ renderWithI18n(
235
+ <UserSettingsForm
236
+ {...baseProps}
237
+ totpEnabled={true}
238
+ onTotpDisable={mockOnTotpDisable}
239
+ onTotpReset={jest.fn()}
240
+ />
241
+ );
242
+
243
+ // Open disable prompt
244
+ fireEvent.click(screen.getByTestId('disable-totp-button'));
245
+
246
+ // Enter code
247
+ const codeInput = screen.getByLabelText(/totp code/i);
248
+ fireEvent.change(codeInput, { target: { value: '654321' } });
249
+
250
+ // Submit
251
+ fireEvent.click(screen.getByTestId('disable-totp-submit-button'));
252
+
253
+ await waitFor(() => {
254
+ expect(mockOnTotpDisable).toHaveBeenCalledWith('654321');
255
+ });
256
+
257
+ // After successful disable, status should update to disabled
258
+ await waitFor(() => {
259
+ expect(
260
+ screen.getByText(/2fa is currently disabled/i)
261
+ ).toBeInTheDocument();
262
+ });
263
+ });
264
+
265
+ /**
266
+ * Validates: Requirement 15.8
267
+ * Clicking "Reset 2FA" shows a 6-digit code prompt.
268
+ */
269
+ it('shows code prompt when clicking "Reset 2FA"', () => {
270
+ renderWithI18n(
271
+ <UserSettingsForm
272
+ {...baseProps}
273
+ totpEnabled={true}
274
+ onTotpDisable={jest.fn()}
275
+ onTotpReset={jest.fn()}
276
+ />
277
+ );
278
+
279
+ fireEvent.click(screen.getByTestId('reset-totp-button'));
280
+
281
+ // Code input should appear
282
+ expect(
283
+ screen.getByText(/enter your current 2fa code to reset/i)
284
+ ).toBeInTheDocument();
285
+ expect(
286
+ screen.getByTestId('reset-totp-submit-button')
287
+ ).toBeInTheDocument();
288
+ expect(
289
+ screen.getByTestId('cancel-reset-totp-button')
290
+ ).toBeInTheDocument();
291
+ });
292
+
293
+ /**
294
+ * Validates: Requirement 15.8
295
+ * Reset flow: entering code and submitting calls onTotpReset, then shows TotpSetupForm.
296
+ */
297
+ it('calls onTotpReset with code and shows TotpSetupForm on success', async () => {
298
+ const mockOnTotpReset = jest.fn().mockResolvedValue({
299
+ provisioningUri:
300
+ 'otpauth://totp/TestIssuer:user@example.com?secret=NEWBASE32SECRET&issuer=TestIssuer',
301
+ secret: 'NEWBASE32SECRET',
302
+ });
303
+
304
+ renderWithI18n(
305
+ <UserSettingsForm
306
+ {...baseProps}
307
+ totpEnabled={true}
308
+ onTotpDisable={jest.fn()}
309
+ onTotpReset={mockOnTotpReset}
310
+ onTotpConfirm={jest.fn()}
311
+ />
312
+ );
313
+
314
+ // Open reset prompt
315
+ fireEvent.click(screen.getByTestId('reset-totp-button'));
316
+
317
+ // Enter code
318
+ const codeInput = screen.getByLabelText(/totp code/i);
319
+ fireEvent.change(codeInput, { target: { value: '123456' } });
320
+
321
+ // Submit
322
+ fireEvent.click(screen.getByTestId('reset-totp-submit-button'));
323
+
324
+ await waitFor(() => {
325
+ expect(mockOnTotpReset).toHaveBeenCalledWith('123456');
326
+ });
327
+
328
+ // TotpSetupForm should appear with the new provisioning URI and secret
329
+ await waitFor(() => {
330
+ expect(screen.getByTestId('qr-code-svg')).toBeInTheDocument();
331
+ });
332
+ expect(screen.getByText('NEWBASE32SECRET')).toBeInTheDocument();
333
+ });
334
+
335
+ /**
336
+ * Validates: Requirement 10.6
337
+ * Disable flow: displays error when onTotpDisable returns an error.
338
+ */
339
+ it('displays error when disable fails', async () => {
340
+ const mockOnTotpDisable = jest
341
+ .fn()
342
+ .mockResolvedValue({ error: 'Invalid TOTP code' });
343
+
344
+ renderWithI18n(
345
+ <UserSettingsForm
346
+ {...baseProps}
347
+ totpEnabled={true}
348
+ onTotpDisable={mockOnTotpDisable}
349
+ onTotpReset={jest.fn()}
350
+ />
351
+ );
352
+
353
+ // Open disable prompt
354
+ fireEvent.click(screen.getByTestId('disable-totp-button'));
355
+
356
+ // Enter code and submit
357
+ const codeInput = screen.getByLabelText(/totp code/i);
358
+ fireEvent.change(codeInput, { target: { value: '000000' } });
359
+ fireEvent.click(screen.getByTestId('disable-totp-submit-button'));
360
+
361
+ await waitFor(() => {
362
+ expect(screen.getByText('Invalid TOTP code')).toBeInTheDocument();
363
+ });
364
+ });
365
+
366
+ /**
367
+ * Validates: Requirement 15.8
368
+ * Reset flow: displays error when onTotpReset returns an error.
369
+ */
370
+ it('displays error when reset fails', async () => {
371
+ const mockOnTotpReset = jest
372
+ .fn()
373
+ .mockResolvedValue({ error: 'Invalid TOTP code' });
374
+
375
+ renderWithI18n(
376
+ <UserSettingsForm
377
+ {...baseProps}
378
+ totpEnabled={true}
379
+ onTotpDisable={jest.fn()}
380
+ onTotpReset={mockOnTotpReset}
381
+ />
382
+ );
383
+
384
+ // Open reset prompt
385
+ fireEvent.click(screen.getByTestId('reset-totp-button'));
386
+
387
+ // Enter code and submit
388
+ const codeInput = screen.getByLabelText(/totp code/i);
389
+ fireEvent.change(codeInput, { target: { value: '000000' } });
390
+ fireEvent.click(screen.getByTestId('reset-totp-submit-button'));
391
+
392
+ await waitFor(() => {
393
+ expect(screen.getByText('Invalid TOTP code')).toBeInTheDocument();
394
+ });
395
+ });
396
+ });
@@ -1,3 +1,4 @@
1
+ // Component exports
1
2
  export * from './ApiAccess';
2
3
  export * from './BackupCodeLoginForm';
3
4
  export * from './BackupCodesForm';
@@ -29,4 +30,3 @@ export * from './TDiv';
29
30
  export * from './TImgAlt';
30
31
  export * from './TotpSetupForm';
31
32
  export * from './TotpVerificationForm';
32
- //# sourceMappingURL=index.d.ts.map