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

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 (314) hide show
  1. package/package.json +5 -4
  2. package/src/auth/AdminRoute.d.ts +8 -0
  3. package/src/auth/AdminRoute.d.ts.map +1 -0
  4. package/src/auth/AdminRoute.js +28 -0
  5. package/src/auth/Private.d.ts +6 -0
  6. package/src/auth/Private.d.ts.map +1 -0
  7. package/src/auth/Private.js +14 -0
  8. package/src/auth/PrivateRoute.d.ts +8 -0
  9. package/src/auth/PrivateRoute.d.ts.map +1 -0
  10. package/src/auth/PrivateRoute.js +23 -0
  11. package/src/auth/UnAuth.d.ts +6 -0
  12. package/src/auth/UnAuth.d.ts.map +1 -0
  13. package/src/auth/UnAuth.js +14 -0
  14. package/src/auth/UnAuthRoute.d.ts +8 -0
  15. package/src/auth/UnAuthRoute.d.ts.map +1 -0
  16. package/src/auth/UnAuthRoute.js +22 -0
  17. package/src/auth/{index.ts → index.d.ts} +3 -2
  18. package/src/auth/index.d.ts.map +1 -0
  19. package/src/auth/index.js +12 -0
  20. package/src/components/ApiAccess.d.ts +16 -0
  21. package/src/components/ApiAccess.d.ts.map +1 -0
  22. package/src/components/ApiAccess.js +77 -0
  23. package/src/components/BackupCodeLoginForm.d.ts +43 -0
  24. package/src/components/BackupCodeLoginForm.d.ts.map +1 -0
  25. package/src/components/BackupCodeLoginForm.js +139 -0
  26. package/src/components/BackupCodesForm.d.ts +26 -0
  27. package/src/components/BackupCodesForm.d.ts.map +1 -0
  28. package/src/components/BackupCodesForm.js +120 -0
  29. package/src/components/CategoryMenu.d.ts +17 -0
  30. package/src/components/CategoryMenu.d.ts.map +1 -0
  31. package/src/components/CategoryMenu.js +65 -0
  32. package/src/components/ChangePasswordForm.d.ts +26 -0
  33. package/src/components/ChangePasswordForm.d.ts.map +1 -0
  34. package/src/components/ChangePasswordForm.js +78 -0
  35. package/src/components/ConfirmationDialog.d.ts +13 -0
  36. package/src/components/ConfirmationDialog.d.ts.map +1 -0
  37. package/src/components/ConfirmationDialog.js +10 -0
  38. package/src/components/CurrencyCodeSelector.d.ts +9 -0
  39. package/src/components/CurrencyCodeSelector.d.ts.map +1 -0
  40. package/src/components/CurrencyCodeSelector.js +31 -0
  41. package/src/components/CurrencyInput.d.ts +13 -0
  42. package/src/components/CurrencyInput.d.ts.map +1 -0
  43. package/src/components/CurrencyInput.js +22 -0
  44. package/src/components/DashboardPage.d.ts +8 -0
  45. package/src/components/DashboardPage.d.ts.map +1 -0
  46. package/src/components/DashboardPage.js +10 -0
  47. package/src/components/DropdownMenu.d.ts +13 -0
  48. package/src/components/DropdownMenu.d.ts.map +1 -0
  49. package/src/components/DropdownMenu.js +63 -0
  50. package/src/components/ExpirationSecondsSelector.d.ts +13 -0
  51. package/src/components/ExpirationSecondsSelector.d.ts.map +1 -0
  52. package/src/components/ExpirationSecondsSelector.js +32 -0
  53. package/src/components/FakeEmailAdminPanel.d.ts +23 -0
  54. package/src/components/FakeEmailAdminPanel.d.ts.map +1 -0
  55. package/src/components/FakeEmailAdminPanel.js +147 -0
  56. package/src/components/Flag.d.ts +20 -0
  57. package/src/components/Flag.d.ts.map +1 -0
  58. package/src/components/Flag.js +43 -0
  59. package/src/components/ForgotPasswordForm.d.ts +18 -0
  60. package/src/components/ForgotPasswordForm.d.ts.map +1 -0
  61. package/src/components/ForgotPasswordForm.js +61 -0
  62. package/src/components/LoginForm.d.ts +44 -0
  63. package/src/components/LoginForm.d.ts.map +1 -0
  64. package/src/components/LoginForm.js +123 -0
  65. package/src/components/LogoutPage.d.ts +8 -0
  66. package/src/components/LogoutPage.d.ts.map +1 -0
  67. package/src/components/LogoutPage.js +16 -0
  68. package/src/components/RegisterForm.d.ts +100 -0
  69. package/src/components/RegisterForm.d.ts.map +1 -0
  70. package/src/components/RegisterForm.js +306 -0
  71. package/src/components/ResetPasswordForm.d.ts +23 -0
  72. package/src/components/ResetPasswordForm.d.ts.map +1 -0
  73. package/src/components/ResetPasswordForm.js +78 -0
  74. package/src/components/SideMenu.d.ts +8 -0
  75. package/src/components/SideMenu.d.ts.map +1 -0
  76. package/src/components/SideMenu.js +25 -0
  77. package/src/components/SideMenuListItem.d.ts +13 -0
  78. package/src/components/SideMenuListItem.d.ts.map +1 -0
  79. package/src/components/SideMenuListItem.js +44 -0
  80. package/src/components/T.d.ts +7 -0
  81. package/src/components/T.d.ts.map +1 -0
  82. package/src/components/T.js +13 -0
  83. package/src/components/TDiv.d.ts +9 -0
  84. package/src/components/TDiv.d.ts.map +1 -0
  85. package/src/components/TDiv.js +14 -0
  86. package/src/components/TImgAlt.d.ts +10 -0
  87. package/src/components/TImgAlt.d.ts.map +1 -0
  88. package/src/components/TImgAlt.js +14 -0
  89. package/src/components/TopMenu.d.ts +50 -0
  90. package/src/components/TopMenu.d.ts.map +1 -0
  91. package/src/components/TopMenu.js +79 -0
  92. package/src/components/TotpSetupForm.d.ts +24 -0
  93. package/src/components/TotpSetupForm.d.ts.map +1 -0
  94. package/src/components/TotpSetupForm.js +82 -0
  95. package/src/components/TotpVerificationForm.d.ts +23 -0
  96. package/src/components/TotpVerificationForm.d.ts.map +1 -0
  97. package/src/components/TotpVerificationForm.js +66 -0
  98. package/src/components/TranslatedTitle.d.ts +8 -0
  99. package/src/components/TranslatedTitle.d.ts.map +1 -0
  100. package/src/components/TranslatedTitle.js +15 -0
  101. package/src/components/UserLanguageSelector.d.ts +4 -0
  102. package/src/components/UserLanguageSelector.d.ts.map +1 -0
  103. package/src/components/UserLanguageSelector.js +31 -0
  104. package/src/components/UserMenu.d.ts +4 -0
  105. package/src/components/UserMenu.d.ts.map +1 -0
  106. package/src/components/UserMenu.js +12 -0
  107. package/src/components/UserSettingsForm.d.ts +113 -0
  108. package/src/components/UserSettingsForm.d.ts.map +1 -0
  109. package/src/components/UserSettingsForm.js +269 -0
  110. package/src/components/VerifyEmailPage.d.ts +24 -0
  111. package/src/components/VerifyEmailPage.d.ts.map +1 -0
  112. package/src/components/VerifyEmailPage.js +81 -0
  113. package/src/components/{index.ts → index.d.ts} +1 -1
  114. package/src/components/index.d.ts.map +1 -0
  115. package/src/components/index.js +35 -0
  116. package/src/contexts/AuthProvider.d.ts +19 -0
  117. package/src/contexts/AuthProvider.d.ts.map +1 -0
  118. package/src/contexts/AuthProvider.js +514 -0
  119. package/src/contexts/I18nProvider.d.ts +27 -0
  120. package/src/contexts/I18nProvider.d.ts.map +1 -0
  121. package/src/contexts/I18nProvider.js +60 -0
  122. package/src/contexts/MenuContext.d.ts +28 -0
  123. package/src/contexts/MenuContext.d.ts.map +1 -0
  124. package/src/contexts/MenuContext.js +307 -0
  125. package/src/contexts/SuiteConfigProvider.d.ts +44 -0
  126. package/src/contexts/SuiteConfigProvider.d.ts.map +1 -0
  127. package/src/contexts/SuiteConfigProvider.js +43 -0
  128. package/src/contexts/ThemeProvider.d.ts +15 -0
  129. package/src/contexts/ThemeProvider.d.ts.map +1 -0
  130. package/src/contexts/ThemeProvider.js +36 -0
  131. package/src/contexts/auth-context.d.ts +172 -0
  132. package/src/contexts/auth-context.d.ts.map +1 -0
  133. package/src/contexts/auth-context.js +13 -0
  134. package/src/contexts/{index.ts → index.d.ts} +1 -0
  135. package/src/contexts/index.d.ts.map +1 -0
  136. package/src/contexts/index.js +8 -0
  137. package/src/hooks/{index.ts → index.d.ts} +1 -0
  138. package/src/hooks/index.d.ts.map +1 -0
  139. package/src/hooks/index.js +9 -0
  140. package/src/hooks/useAuthenticatedApi.d.ts +34 -0
  141. package/src/hooks/useAuthenticatedApi.d.ts.map +1 -0
  142. package/src/hooks/useAuthenticatedApi.js +46 -0
  143. package/src/hooks/useBackupCodes.d.ts +15 -0
  144. package/src/hooks/useBackupCodes.d.ts.map +1 -0
  145. package/src/hooks/useBackupCodes.js +74 -0
  146. package/src/hooks/useEmailVerification.d.ts +10 -0
  147. package/src/hooks/useEmailVerification.d.ts.map +1 -0
  148. package/src/hooks/useEmailVerification.js +40 -0
  149. package/src/hooks/useExpiringValue.d.ts +14 -0
  150. package/src/hooks/useExpiringValue.d.ts.map +1 -0
  151. package/src/hooks/useExpiringValue.js +53 -0
  152. package/src/hooks/useLocalStorage.d.ts +2 -0
  153. package/src/hooks/useLocalStorage.d.ts.map +1 -0
  154. package/src/hooks/useLocalStorage.js +15 -0
  155. package/src/hooks/useUserSettings.d.ts +48 -0
  156. package/src/hooks/useUserSettings.d.ts.map +1 -0
  157. package/src/hooks/useUserSettings.js +171 -0
  158. package/src/{index.ts → index.d.ts} +1 -1
  159. package/src/index.d.ts.map +1 -0
  160. package/src/index.js +12 -0
  161. package/src/interfaces/IAppConfig.d.ts +8 -0
  162. package/src/interfaces/IAppConfig.d.ts.map +1 -0
  163. package/src/interfaces/IAppConfig.js +2 -0
  164. package/src/interfaces/ICategoryConfig.d.ts +41 -0
  165. package/src/interfaces/ICategoryConfig.d.ts.map +1 -0
  166. package/src/interfaces/ICategoryConfig.js +2 -0
  167. package/src/interfaces/IMenuConfig.d.ts +28 -0
  168. package/src/interfaces/IMenuConfig.d.ts.map +1 -0
  169. package/src/interfaces/IMenuConfig.js +2 -0
  170. package/src/interfaces/IMenuOption.d.ts +82 -0
  171. package/src/interfaces/IMenuOption.d.ts.map +1 -0
  172. package/src/interfaces/IMenuOption.js +2 -0
  173. package/src/interfaces/{index.ts → index.d.ts} +1 -0
  174. package/src/interfaces/index.d.ts.map +1 -0
  175. package/src/interfaces/index.js +7 -0
  176. package/src/services/__mocks__/authService.d.ts +21 -0
  177. package/src/services/__mocks__/authService.d.ts.map +1 -0
  178. package/src/services/__mocks__/authService.js +15 -0
  179. package/src/services/api.d.ts +3 -0
  180. package/src/services/api.d.ts.map +1 -0
  181. package/src/services/api.js +14 -0
  182. package/src/services/authService.d.ts +126 -0
  183. package/src/services/authService.d.ts.map +1 -0
  184. package/src/services/authService.js +471 -0
  185. package/src/services/authenticatedApi.d.ts +3 -0
  186. package/src/services/authenticatedApi.d.ts.map +1 -0
  187. package/src/services/authenticatedApi.js +18 -0
  188. package/src/services/index.d.ts +4 -0
  189. package/src/services/index.d.ts.map +1 -0
  190. package/src/services/index.js +6 -0
  191. package/src/types/MenuType.d.ts +11 -0
  192. package/src/types/MenuType.d.ts.map +1 -0
  193. package/src/types/MenuType.js +12 -0
  194. package/src/types/expirationSeconds.d.ts +3 -0
  195. package/src/types/expirationSeconds.d.ts.map +1 -0
  196. package/src/types/expirationSeconds.js +17 -0
  197. package/src/types/index.d.ts +2 -0
  198. package/src/types/index.d.ts.map +1 -0
  199. package/src/types/index.js +4 -0
  200. package/src/types/translation.d.ts +10 -0
  201. package/src/types/translation.d.ts.map +1 -0
  202. package/src/types/translation.js +9 -0
  203. package/src/wrappers/BackupCodeLoginWrapper.d.ts +8 -0
  204. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +1 -0
  205. package/src/wrappers/BackupCodeLoginWrapper.js +20 -0
  206. package/src/wrappers/BackupCodesWrapper.d.ts +7 -0
  207. package/src/wrappers/BackupCodesWrapper.d.ts.map +1 -0
  208. package/src/wrappers/BackupCodesWrapper.js +17 -0
  209. package/src/wrappers/ChangePasswordFormWrapper.d.ts +8 -0
  210. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +1 -0
  211. package/src/wrappers/ChangePasswordFormWrapper.js +21 -0
  212. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +8 -0
  213. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +1 -0
  214. package/src/wrappers/ForgotPasswordFormWrapper.js +29 -0
  215. package/src/wrappers/LoginFormWrapper.d.ts +21 -0
  216. package/src/wrappers/LoginFormWrapper.d.ts.map +1 -0
  217. package/src/wrappers/LoginFormWrapper.js +72 -0
  218. package/src/wrappers/LogoutPageWrapper.d.ts +9 -0
  219. package/src/wrappers/LogoutPageWrapper.d.ts.map +1 -0
  220. package/src/wrappers/LogoutPageWrapper.js +21 -0
  221. package/src/wrappers/RegisterFormWrapper.d.ts +20 -0
  222. package/src/wrappers/RegisterFormWrapper.d.ts.map +1 -0
  223. package/src/wrappers/RegisterFormWrapper.js +51 -0
  224. package/src/wrappers/ResetPasswordFormWrapper.d.ts +9 -0
  225. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +1 -0
  226. package/src/wrappers/ResetPasswordFormWrapper.js +34 -0
  227. package/src/wrappers/UserSettingsFormWrapper.d.ts +29 -0
  228. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +1 -0
  229. package/src/wrappers/UserSettingsFormWrapper.js +95 -0
  230. package/src/wrappers/VerifyEmailPageWrapper.d.ts +8 -0
  231. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +1 -0
  232. package/src/wrappers/VerifyEmailPageWrapper.js +21 -0
  233. package/src/wrappers/{index.tsx → index.d.ts} +1 -10
  234. package/src/wrappers/index.d.ts.map +1 -0
  235. package/src/wrappers/index.js +24 -0
  236. package/LICENSE +0 -21
  237. package/src/auth/AdminRoute.tsx +0 -54
  238. package/src/auth/Private.tsx +0 -17
  239. package/src/auth/PrivateRoute.tsx +0 -28
  240. package/src/auth/UnAuth.tsx +0 -16
  241. package/src/auth/UnAuthRoute.tsx +0 -30
  242. package/src/components/ApiAccess.tsx +0 -175
  243. package/src/components/BackupCodeLoginForm.tsx +0 -489
  244. package/src/components/BackupCodesForm.tsx +0 -287
  245. package/src/components/CategoryMenu.tsx +0 -155
  246. package/src/components/ChangePasswordForm.tsx +0 -273
  247. package/src/components/ConfirmationDialog.tsx +0 -48
  248. package/src/components/CurrencyCodeSelector.tsx +0 -60
  249. package/src/components/CurrencyInput.tsx +0 -80
  250. package/src/components/DashboardPage.tsx +0 -24
  251. package/src/components/DropdownMenu.tsx +0 -105
  252. package/src/components/ExpirationSecondsSelector.tsx +0 -60
  253. package/src/components/FakeEmailAdminPanel.tsx +0 -374
  254. package/src/components/Flag.tsx +0 -52
  255. package/src/components/ForgotPasswordForm.tsx +0 -174
  256. package/src/components/LoginForm.tsx +0 -468
  257. package/src/components/LogoutPage.tsx +0 -21
  258. package/src/components/RegisterForm.tsx +0 -1169
  259. package/src/components/ResetPasswordForm.tsx +0 -247
  260. package/src/components/SideMenu.tsx +0 -46
  261. package/src/components/SideMenuListItem.tsx +0 -75
  262. package/src/components/T.tsx +0 -24
  263. package/src/components/TDiv.tsx +0 -26
  264. package/src/components/TImgAlt.tsx +0 -28
  265. package/src/components/TopMenu.tsx +0 -252
  266. package/src/components/TotpSetupForm.tsx +0 -188
  267. package/src/components/TotpVerificationForm.tsx +0 -148
  268. package/src/components/TranslatedTitle.tsx +0 -31
  269. package/src/components/UserLanguageSelector.tsx +0 -45
  270. package/src/components/UserMenu.tsx +0 -15
  271. package/src/components/UserSettingsForm.tsx +0 -904
  272. package/src/components/VerifyEmailPage.tsx +0 -222
  273. package/src/components/__tests__/RegisterForm.totp.spec.tsx +0 -377
  274. package/src/components/__tests__/TotpSetupForm.spec.tsx +0 -258
  275. package/src/components/__tests__/TotpVerificationForm.spec.tsx +0 -259
  276. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +0 -396
  277. package/src/contexts/AuthProvider.spec.tsx +0 -1284
  278. package/src/contexts/AuthProvider.tsx +0 -977
  279. package/src/contexts/I18nProvider.tsx +0 -164
  280. package/src/contexts/MenuContext.tsx +0 -426
  281. package/src/contexts/SuiteConfigProvider.tsx +0 -93
  282. package/src/contexts/ThemeProvider.tsx +0 -67
  283. package/src/hooks/useAuthenticatedApi.tsx +0 -67
  284. package/src/hooks/useBackupCodes.ts +0 -105
  285. package/src/hooks/useEmailVerification.ts +0 -54
  286. package/src/hooks/useExpiringValue.ts +0 -78
  287. package/src/hooks/useLocalStorage.ts +0 -18
  288. package/src/hooks/useUserSettings.ts +0 -271
  289. package/src/interfaces/IAppConfig.ts +0 -7
  290. package/src/interfaces/ICategoryConfig.ts +0 -41
  291. package/src/interfaces/IMenuConfig.ts +0 -28
  292. package/src/interfaces/IMenuOption.ts +0 -79
  293. package/src/services/__mocks__/authService.ts +0 -14
  294. package/src/services/__tests__/authService.totp.property.spec.ts +0 -360
  295. package/src/services/__tests__/authService.totp.spec.ts +0 -439
  296. package/src/services/api.ts +0 -13
  297. package/src/services/authService.ts +0 -692
  298. package/src/services/authenticatedApi.ts +0 -17
  299. package/src/services/index.ts +0 -3
  300. package/src/types/MenuType.ts +0 -15
  301. package/src/types/expirationSeconds.ts +0 -18
  302. package/src/types/index.ts +0 -1
  303. package/src/types/translation.ts +0 -20
  304. package/src/wrappers/BackupCodeLoginWrapper.tsx +0 -34
  305. package/src/wrappers/BackupCodesWrapper.tsx +0 -28
  306. package/src/wrappers/ChangePasswordFormWrapper.tsx +0 -34
  307. package/src/wrappers/ForgotPasswordFormWrapper.tsx +0 -45
  308. package/src/wrappers/LoginFormWrapper.tsx +0 -108
  309. package/src/wrappers/LogoutPageWrapper.tsx +0 -30
  310. package/src/wrappers/RegisterFormWrapper.tsx +0 -109
  311. package/src/wrappers/ResetPasswordFormWrapper.tsx +0 -57
  312. package/src/wrappers/UserSettingsFormWrapper.tsx +0 -140
  313. package/src/wrappers/VerifyEmailPageWrapper.tsx +0 -27
  314. package/src/wrappers/__tests__/LoginFormWrapper.totp.spec.tsx +0 -339
@@ -1,258 +0,0 @@
1
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2
- import { TotpSetupForm, TotpSetupFormProps } from '../TotpSetupForm';
3
-
4
- // Mock qrcode.react to avoid SVG rendering complexity in jsdom
5
- jest.mock('qrcode.react', () => ({
6
- QRCodeSVG: (props: Record<string, unknown>) => (
7
- <svg
8
- data-testid="qr-code-svg"
9
- role={props.role as string}
10
- aria-label={props['aria-label'] as string}
11
- />
12
- ),
13
- }));
14
-
15
- const defaultProps: TotpSetupFormProps = {
16
- provisioningUri:
17
- 'otpauth://totp/TestIssuer:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=TestIssuer',
18
- secret: 'JBSWY3DPEHPK3PXP',
19
- onConfirm: jest.fn().mockResolvedValue({ success: true }),
20
- };
21
-
22
- describe('TotpSetupForm', () => {
23
- // Suppress act() warnings from Formik's internal state management
24
- const originalError = console.error;
25
- beforeAll(() => {
26
- console.error = (...args: unknown[]) => {
27
- if (
28
- typeof args[0] === 'string' &&
29
- args[0].includes('not wrapped in act')
30
- ) {
31
- return;
32
- }
33
- originalError.call(console, ...args);
34
- };
35
- });
36
-
37
- afterAll(() => {
38
- console.error = originalError;
39
- });
40
-
41
- beforeEach(() => {
42
- jest.clearAllMocks();
43
- });
44
-
45
- it('renders QR code from provisioningUri', () => {
46
- render(<TotpSetupForm {...defaultProps} />);
47
-
48
- const qrCode = screen.getByTestId('qr-code-svg');
49
- expect(qrCode).toBeInTheDocument();
50
- expect(qrCode).toHaveAttribute('role', 'img');
51
- });
52
-
53
- it('renders QR code with descriptive aria-label', () => {
54
- render(<TotpSetupForm {...defaultProps} />);
55
-
56
- const qrCode = screen.getByRole('img', {
57
- name: /qr code for totp authenticator app setup/i,
58
- });
59
- expect(qrCode).toBeInTheDocument();
60
- });
61
-
62
- it('renders QR code with custom aria-label from labels prop', () => {
63
- render(
64
- <TotpSetupForm
65
- {...defaultProps}
66
- labels={{ qrCodeAlt: 'Custom QR description' }}
67
- />
68
- );
69
-
70
- const qrCode = screen.getByRole('img', { name: 'Custom QR description' });
71
- expect(qrCode).toBeInTheDocument();
72
- });
73
-
74
- it('displays the raw base32 secret as a fallback', () => {
75
- render(<TotpSetupForm {...defaultProps} />);
76
-
77
- expect(screen.getByText('JBSWY3DPEHPK3PXP')).toBeInTheDocument();
78
- expect(screen.getByText(/manual entry key/i)).toBeInTheDocument();
79
- });
80
-
81
- it('renders a 6-digit confirmation code input with aria-label', () => {
82
- render(<TotpSetupForm {...defaultProps} />);
83
-
84
- const input = screen.getByLabelText(/confirmation code/i);
85
- expect(input).toBeInTheDocument();
86
- expect(input).toHaveAttribute('inputmode', 'numeric');
87
- expect(input).toHaveAttribute('maxlength', '6');
88
- });
89
-
90
- it('renders a confirm button', () => {
91
- render(<TotpSetupForm {...defaultProps} />);
92
-
93
- const button = screen.getByRole('button', { name: /confirm/i });
94
- expect(button).toBeInTheDocument();
95
- expect(button).not.toBeDisabled();
96
- });
97
-
98
- it('calls onConfirm with the entered code on form submission', async () => {
99
- const mockOnConfirm = jest.fn().mockResolvedValue({ success: true });
100
- render(<TotpSetupForm {...defaultProps} onConfirm={mockOnConfirm} />);
101
-
102
- const input = screen.getByLabelText(/confirmation code/i);
103
- fireEvent.change(input, { target: { value: '123456' } });
104
-
105
- const button = screen.getByRole('button', { name: /confirm/i });
106
- fireEvent.click(button);
107
-
108
- await waitFor(() => {
109
- expect(mockOnConfirm).toHaveBeenCalledWith('123456');
110
- });
111
- });
112
-
113
- it('displays error message when onConfirm returns an error', async () => {
114
- const mockOnConfirm = jest
115
- .fn()
116
- .mockResolvedValue({ error: 'Invalid TOTP code' });
117
- render(<TotpSetupForm {...defaultProps} onConfirm={mockOnConfirm} />);
118
-
119
- const input = screen.getByLabelText(/confirmation code/i);
120
- fireEvent.change(input, { target: { value: '000000' } });
121
-
122
- const button = screen.getByRole('button', { name: /confirm/i });
123
- fireEvent.click(button);
124
-
125
- await waitFor(() => {
126
- expect(screen.getByText('Invalid TOTP code')).toBeInTheDocument();
127
- });
128
-
129
- // Error message should have role="alert" for accessibility
130
- expect(screen.getByRole('alert')).toBeInTheDocument();
131
- });
132
-
133
- it('displays fallback error message when onConfirm throws', async () => {
134
- const mockOnConfirm = jest
135
- .fn()
136
- .mockRejectedValue(new Error('Network error'));
137
- render(<TotpSetupForm {...defaultProps} onConfirm={mockOnConfirm} />);
138
-
139
- const input = screen.getByLabelText(/confirmation code/i);
140
- fireEvent.change(input, { target: { value: '123456' } });
141
-
142
- const button = screen.getByRole('button', { name: /confirm/i });
143
- fireEvent.click(button);
144
-
145
- await waitFor(() => {
146
- expect(
147
- screen.getByText(/an unexpected error occurred/i)
148
- ).toBeInTheDocument();
149
- });
150
- });
151
-
152
- it('error message is associated with input via aria-describedby', async () => {
153
- const mockOnConfirm = jest
154
- .fn()
155
- .mockResolvedValue({ error: 'Code expired' });
156
- render(<TotpSetupForm {...defaultProps} onConfirm={mockOnConfirm} />);
157
-
158
- const input = screen.getByLabelText(/confirmation code/i);
159
- fireEvent.change(input, { target: { value: '111111' } });
160
-
161
- const button = screen.getByRole('button', { name: /confirm/i });
162
- fireEvent.click(button);
163
-
164
- await waitFor(() => {
165
- const errorElement = screen.getByText('Code expired');
166
- expect(errorElement).toHaveAttribute('id', 'totp-setup-error');
167
- });
168
-
169
- // After error appears, the input should reference the error via aria-describedby
170
- expect(input).toHaveAttribute('aria-describedby', 'totp-setup-error');
171
- });
172
-
173
- it('validates that code must be exactly 6 digits', async () => {
174
- render(<TotpSetupForm {...defaultProps} />);
175
-
176
- const input = screen.getByLabelText(/confirmation code/i);
177
- fireEvent.change(input, { target: { value: '123' } });
178
- fireEvent.blur(input);
179
-
180
- await waitFor(() => {
181
- expect(
182
- screen.getByText(/code must be exactly 6 digits/i)
183
- ).toBeInTheDocument();
184
- });
185
- });
186
-
187
- it('validates that code is required', async () => {
188
- render(<TotpSetupForm {...defaultProps} />);
189
-
190
- const input = screen.getByLabelText(/confirmation code/i);
191
- fireEvent.focus(input);
192
- fireEvent.blur(input);
193
-
194
- await waitFor(() => {
195
- expect(
196
- screen.getByText(/confirmation code is required/i)
197
- ).toBeInTheDocument();
198
- });
199
- });
200
-
201
- it('uses custom labels when provided', () => {
202
- render(
203
- <TotpSetupForm
204
- {...defaultProps}
205
- labels={{
206
- title: 'Custom Title',
207
- secretLabel: 'Secret Key',
208
- codeLabel: 'Enter Code',
209
- confirmButton: 'Verify',
210
- }}
211
- />
212
- );
213
-
214
- expect(screen.getByText('Custom Title')).toBeInTheDocument();
215
- expect(screen.getByText('Secret Key')).toBeInTheDocument();
216
- expect(screen.getByLabelText('Enter Code')).toBeInTheDocument();
217
- expect(screen.getByRole('button', { name: 'Verify' })).toBeInTheDocument();
218
- });
219
-
220
- it('renders the title heading', () => {
221
- render(<TotpSetupForm {...defaultProps} />);
222
-
223
- expect(
224
- screen.getByRole('heading', {
225
- name: /set up two-factor authentication/i,
226
- })
227
- ).toBeInTheDocument();
228
- });
229
-
230
- it('disables submit button while submitting', async () => {
231
- // Create a promise that we control to keep the form in submitting state
232
- let resolveConfirm: (value: { success: boolean }) => void;
233
- const mockOnConfirm = jest.fn(
234
- () =>
235
- new Promise<{ success: boolean }>((resolve) => {
236
- resolveConfirm = resolve;
237
- })
238
- );
239
- render(<TotpSetupForm {...defaultProps} onConfirm={mockOnConfirm} />);
240
-
241
- const input = screen.getByLabelText(/confirmation code/i);
242
- fireEvent.change(input, { target: { value: '123456' } });
243
-
244
- const button = screen.getByRole('button', { name: /confirm/i });
245
- fireEvent.click(button);
246
-
247
- await waitFor(() => {
248
- expect(button).toBeDisabled();
249
- });
250
-
251
- // Resolve the promise to complete submission
252
- resolveConfirm!({ success: true });
253
-
254
- await waitFor(() => {
255
- expect(button).not.toBeDisabled();
256
- });
257
- });
258
- });
@@ -1,259 +0,0 @@
1
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2
- import {
3
- TotpVerificationForm,
4
- TotpVerificationFormProps,
5
- } from '../TotpVerificationForm';
6
-
7
- const defaultProps: TotpVerificationFormProps = {
8
- pendingTotpToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test-pending-token',
9
- onSubmit: jest.fn().mockResolvedValue({ token: 'full-jwt', user: {} }),
10
- };
11
-
12
- describe('TotpVerificationForm', () => {
13
- // Suppress act() warnings from Formik's internal state management
14
- const originalError = console.error;
15
- beforeAll(() => {
16
- console.error = (...args: unknown[]) => {
17
- if (
18
- typeof args[0] === 'string' &&
19
- args[0].includes('not wrapped in act')
20
- ) {
21
- return;
22
- }
23
- originalError.call(console, ...args);
24
- };
25
- });
26
-
27
- afterAll(() => {
28
- console.error = originalError;
29
- });
30
-
31
- beforeEach(() => {
32
- jest.clearAllMocks();
33
- });
34
-
35
- it('renders a 6-digit code input field', () => {
36
- render(<TotpVerificationForm {...defaultProps} />);
37
-
38
- const input = screen.getByLabelText(/authentication code/i);
39
- expect(input).toBeInTheDocument();
40
- expect(input).toHaveAttribute('inputmode', 'numeric');
41
- expect(input).toHaveAttribute('maxlength', '6');
42
- });
43
-
44
- it('renders a submit button', () => {
45
- render(<TotpVerificationForm {...defaultProps} />);
46
-
47
- const button = screen.getByRole('button', { name: /verify/i });
48
- expect(button).toBeInTheDocument();
49
- expect(button).not.toBeDisabled();
50
- });
51
-
52
- it('renders the title heading', () => {
53
- render(<TotpVerificationForm {...defaultProps} />);
54
-
55
- expect(
56
- screen.getByRole('heading', {
57
- name: /two-factor authentication/i,
58
- })
59
- ).toBeInTheDocument();
60
- });
61
-
62
- it('calls onSubmit with the entered code and pendingTotpToken', async () => {
63
- const mockOnSubmit = jest
64
- .fn()
65
- .mockResolvedValue({ token: 'full-jwt', user: {} });
66
- render(
67
- <TotpVerificationForm {...defaultProps} onSubmit={mockOnSubmit} />
68
- );
69
-
70
- const input = screen.getByLabelText(/authentication code/i);
71
- fireEvent.change(input, { target: { value: '654321' } });
72
-
73
- const button = screen.getByRole('button', { name: /verify/i });
74
- fireEvent.click(button);
75
-
76
- await waitFor(() => {
77
- expect(mockOnSubmit).toHaveBeenCalledWith(
78
- '654321',
79
- defaultProps.pendingTotpToken
80
- );
81
- });
82
- });
83
-
84
- it('displays error message when onSubmit returns an error', async () => {
85
- const mockOnSubmit = jest
86
- .fn()
87
- .mockResolvedValue({ error: 'Invalid TOTP code' });
88
- render(
89
- <TotpVerificationForm {...defaultProps} onSubmit={mockOnSubmit} />
90
- );
91
-
92
- const input = screen.getByLabelText(/authentication code/i);
93
- fireEvent.change(input, { target: { value: '000000' } });
94
-
95
- const button = screen.getByRole('button', { name: /verify/i });
96
- fireEvent.click(button);
97
-
98
- await waitFor(() => {
99
- expect(screen.getByText('Invalid TOTP code')).toBeInTheDocument();
100
- });
101
-
102
- // Error message should have role="alert" for accessibility
103
- expect(screen.getByRole('alert')).toBeInTheDocument();
104
- });
105
-
106
- it('displays fallback error message when onSubmit throws', async () => {
107
- const mockOnSubmit = jest
108
- .fn()
109
- .mockRejectedValue(new Error('Network error'));
110
- render(
111
- <TotpVerificationForm {...defaultProps} onSubmit={mockOnSubmit} />
112
- );
113
-
114
- const input = screen.getByLabelText(/authentication code/i);
115
- fireEvent.change(input, { target: { value: '123456' } });
116
-
117
- const button = screen.getByRole('button', { name: /verify/i });
118
- fireEvent.click(button);
119
-
120
- await waitFor(() => {
121
- expect(
122
- screen.getByText(/an unexpected error occurred/i)
123
- ).toBeInTheDocument();
124
- });
125
- });
126
-
127
- it('disables submit button while submitting (loading state)', async () => {
128
- let resolveSubmit: (value: { token: string; user: object }) => void;
129
- const mockOnSubmit = jest.fn(
130
- () =>
131
- new Promise<{ token: string; user: object }>((resolve) => {
132
- resolveSubmit = resolve;
133
- })
134
- );
135
- render(
136
- <TotpVerificationForm {...defaultProps} onSubmit={mockOnSubmit} />
137
- );
138
-
139
- const input = screen.getByLabelText(/authentication code/i);
140
- fireEvent.change(input, { target: { value: '123456' } });
141
-
142
- const button = screen.getByRole('button', { name: /verify/i });
143
- fireEvent.click(button);
144
-
145
- await waitFor(() => {
146
- expect(button).toBeDisabled();
147
- });
148
-
149
- // Resolve the promise to complete submission
150
- resolveSubmit!({ token: 'jwt', user: {} });
151
-
152
- await waitFor(() => {
153
- expect(button).not.toBeDisabled();
154
- });
155
- });
156
-
157
- it('error message is associated with input via aria-describedby', async () => {
158
- const mockOnSubmit = jest
159
- .fn()
160
- .mockResolvedValue({ error: 'Code expired' });
161
- render(
162
- <TotpVerificationForm {...defaultProps} onSubmit={mockOnSubmit} />
163
- );
164
-
165
- const input = screen.getByLabelText(/authentication code/i);
166
- fireEvent.change(input, { target: { value: '111111' } });
167
-
168
- const button = screen.getByRole('button', { name: /verify/i });
169
- fireEvent.click(button);
170
-
171
- await waitFor(() => {
172
- const errorElement = screen.getByText('Code expired');
173
- expect(errorElement).toHaveAttribute('id', 'totp-verification-error');
174
- });
175
-
176
- // The input should reference the error via aria-describedby
177
- expect(input).toHaveAttribute(
178
- 'aria-describedby',
179
- 'totp-verification-error'
180
- );
181
- });
182
-
183
- it('validates that code must be exactly 6 digits', async () => {
184
- render(<TotpVerificationForm {...defaultProps} />);
185
-
186
- const input = screen.getByLabelText(/authentication code/i);
187
- fireEvent.change(input, { target: { value: '123' } });
188
- fireEvent.blur(input);
189
-
190
- await waitFor(() => {
191
- expect(
192
- screen.getByText(/code must be exactly 6 digits/i)
193
- ).toBeInTheDocument();
194
- });
195
- });
196
-
197
- it('validates that code is required', async () => {
198
- render(<TotpVerificationForm {...defaultProps} />);
199
-
200
- const input = screen.getByLabelText(/authentication code/i);
201
- fireEvent.focus(input);
202
- fireEvent.blur(input);
203
-
204
- await waitFor(() => {
205
- expect(
206
- screen.getByText(/authentication code is required/i)
207
- ).toBeInTheDocument();
208
- });
209
- });
210
-
211
- it('uses custom labels when provided', () => {
212
- render(
213
- <TotpVerificationForm
214
- {...defaultProps}
215
- labels={{
216
- title: 'Enter Your Code',
217
- codeLabel: 'TOTP Code',
218
- submitButton: 'Submit Code',
219
- }}
220
- />
221
- );
222
-
223
- expect(screen.getByText('Enter Your Code')).toBeInTheDocument();
224
- expect(screen.getByLabelText('TOTP Code')).toBeInTheDocument();
225
- expect(
226
- screen.getByRole('button', { name: 'Submit Code' })
227
- ).toBeInTheDocument();
228
- });
229
-
230
- it('uses custom error message label on unexpected error', async () => {
231
- const mockOnSubmit = jest
232
- .fn()
233
- .mockRejectedValue(new Error('Network error'));
234
- render(
235
- <TotpVerificationForm
236
- {...defaultProps}
237
- onSubmit={mockOnSubmit}
238
- labels={{ errorMessage: 'Something went wrong' }}
239
- />
240
- );
241
-
242
- const input = screen.getByLabelText(/authentication code/i);
243
- fireEvent.change(input, { target: { value: '123456' } });
244
-
245
- const button = screen.getByRole('button', { name: /verify/i });
246
- fireEvent.click(button);
247
-
248
- await waitFor(() => {
249
- expect(screen.getByText('Something went wrong')).toBeInTheDocument();
250
- });
251
- });
252
-
253
- it('input has aria-label for accessibility', () => {
254
- render(<TotpVerificationForm {...defaultProps} />);
255
-
256
- const input = screen.getByLabelText(/authentication code/i);
257
- expect(input).toHaveAttribute('aria-label', 'Authentication code');
258
- });
259
- });