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

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/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 +186 -0
  117. package/src/contexts/AuthProvider.d.ts.map +1 -0
  118. package/src/contexts/AuthProvider.js +515 -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 +292 -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/{index.ts → index.d.ts} +1 -0
  132. package/src/contexts/index.d.ts.map +1 -0
  133. package/src/contexts/index.js +8 -0
  134. package/src/hooks/{index.ts → index.d.ts} +1 -0
  135. package/src/hooks/index.d.ts.map +1 -0
  136. package/src/hooks/index.js +9 -0
  137. package/src/hooks/useAuthenticatedApi.d.ts +34 -0
  138. package/src/hooks/useAuthenticatedApi.d.ts.map +1 -0
  139. package/src/hooks/useAuthenticatedApi.js +46 -0
  140. package/src/hooks/useBackupCodes.d.ts +15 -0
  141. package/src/hooks/useBackupCodes.d.ts.map +1 -0
  142. package/src/hooks/useBackupCodes.js +74 -0
  143. package/src/hooks/useEmailVerification.d.ts +10 -0
  144. package/src/hooks/useEmailVerification.d.ts.map +1 -0
  145. package/src/hooks/useEmailVerification.js +40 -0
  146. package/src/hooks/useExpiringValue.d.ts +14 -0
  147. package/src/hooks/useExpiringValue.d.ts.map +1 -0
  148. package/src/hooks/useExpiringValue.js +53 -0
  149. package/src/hooks/useLocalStorage.d.ts +2 -0
  150. package/src/hooks/useLocalStorage.d.ts.map +1 -0
  151. package/src/hooks/useLocalStorage.js +15 -0
  152. package/src/hooks/useUserSettings.d.ts +48 -0
  153. package/src/hooks/useUserSettings.d.ts.map +1 -0
  154. package/src/hooks/useUserSettings.js +171 -0
  155. package/src/{index.ts → index.d.ts} +1 -1
  156. package/src/index.d.ts.map +1 -0
  157. package/src/index.js +12 -0
  158. package/src/interfaces/IAppConfig.d.ts +8 -0
  159. package/src/interfaces/IAppConfig.d.ts.map +1 -0
  160. package/src/interfaces/IAppConfig.js +2 -0
  161. package/src/interfaces/ICategoryConfig.d.ts +41 -0
  162. package/src/interfaces/ICategoryConfig.d.ts.map +1 -0
  163. package/src/interfaces/ICategoryConfig.js +2 -0
  164. package/src/interfaces/IMenuConfig.d.ts +28 -0
  165. package/src/interfaces/IMenuConfig.d.ts.map +1 -0
  166. package/src/interfaces/IMenuConfig.js +2 -0
  167. package/src/interfaces/IMenuOption.d.ts +82 -0
  168. package/src/interfaces/IMenuOption.d.ts.map +1 -0
  169. package/src/interfaces/IMenuOption.js +2 -0
  170. package/src/interfaces/{index.ts → index.d.ts} +1 -0
  171. package/src/interfaces/index.d.ts.map +1 -0
  172. package/src/interfaces/index.js +7 -0
  173. package/src/services/__mocks__/authService.d.ts +21 -0
  174. package/src/services/__mocks__/authService.d.ts.map +1 -0
  175. package/src/services/__mocks__/authService.js +15 -0
  176. package/src/services/api.d.ts +3 -0
  177. package/src/services/api.d.ts.map +1 -0
  178. package/src/services/api.js +14 -0
  179. package/src/services/authService.d.ts +126 -0
  180. package/src/services/authService.d.ts.map +1 -0
  181. package/src/services/authService.js +471 -0
  182. package/src/services/authenticatedApi.d.ts +3 -0
  183. package/src/services/authenticatedApi.d.ts.map +1 -0
  184. package/src/services/authenticatedApi.js +18 -0
  185. package/src/services/index.d.ts +4 -0
  186. package/src/services/index.d.ts.map +1 -0
  187. package/src/services/index.js +6 -0
  188. package/src/types/MenuType.d.ts +11 -0
  189. package/src/types/MenuType.d.ts.map +1 -0
  190. package/src/types/MenuType.js +12 -0
  191. package/src/types/expirationSeconds.d.ts +3 -0
  192. package/src/types/expirationSeconds.d.ts.map +1 -0
  193. package/src/types/expirationSeconds.js +17 -0
  194. package/src/types/index.d.ts +2 -0
  195. package/src/types/index.d.ts.map +1 -0
  196. package/src/types/index.js +4 -0
  197. package/src/types/translation.d.ts +10 -0
  198. package/src/types/translation.d.ts.map +1 -0
  199. package/src/types/translation.js +9 -0
  200. package/src/wrappers/BackupCodeLoginWrapper.d.ts +8 -0
  201. package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +1 -0
  202. package/src/wrappers/BackupCodeLoginWrapper.js +20 -0
  203. package/src/wrappers/BackupCodesWrapper.d.ts +7 -0
  204. package/src/wrappers/BackupCodesWrapper.d.ts.map +1 -0
  205. package/src/wrappers/BackupCodesWrapper.js +17 -0
  206. package/src/wrappers/ChangePasswordFormWrapper.d.ts +8 -0
  207. package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +1 -0
  208. package/src/wrappers/ChangePasswordFormWrapper.js +21 -0
  209. package/src/wrappers/ForgotPasswordFormWrapper.d.ts +8 -0
  210. package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +1 -0
  211. package/src/wrappers/ForgotPasswordFormWrapper.js +29 -0
  212. package/src/wrappers/LoginFormWrapper.d.ts +21 -0
  213. package/src/wrappers/LoginFormWrapper.d.ts.map +1 -0
  214. package/src/wrappers/LoginFormWrapper.js +72 -0
  215. package/src/wrappers/LogoutPageWrapper.d.ts +9 -0
  216. package/src/wrappers/LogoutPageWrapper.d.ts.map +1 -0
  217. package/src/wrappers/LogoutPageWrapper.js +21 -0
  218. package/src/wrappers/RegisterFormWrapper.d.ts +20 -0
  219. package/src/wrappers/RegisterFormWrapper.d.ts.map +1 -0
  220. package/src/wrappers/RegisterFormWrapper.js +51 -0
  221. package/src/wrappers/ResetPasswordFormWrapper.d.ts +9 -0
  222. package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +1 -0
  223. package/src/wrappers/ResetPasswordFormWrapper.js +34 -0
  224. package/src/wrappers/UserSettingsFormWrapper.d.ts +29 -0
  225. package/src/wrappers/UserSettingsFormWrapper.d.ts.map +1 -0
  226. package/src/wrappers/UserSettingsFormWrapper.js +95 -0
  227. package/src/wrappers/VerifyEmailPageWrapper.d.ts +8 -0
  228. package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +1 -0
  229. package/src/wrappers/VerifyEmailPageWrapper.js +21 -0
  230. package/src/wrappers/{index.tsx → index.d.ts} +1 -10
  231. package/src/wrappers/index.d.ts.map +1 -0
  232. package/src/wrappers/index.js +24 -0
  233. package/LICENSE +0 -21
  234. package/src/auth/AdminRoute.tsx +0 -54
  235. package/src/auth/Private.tsx +0 -17
  236. package/src/auth/PrivateRoute.tsx +0 -28
  237. package/src/auth/UnAuth.tsx +0 -16
  238. package/src/auth/UnAuthRoute.tsx +0 -30
  239. package/src/components/ApiAccess.tsx +0 -175
  240. package/src/components/BackupCodeLoginForm.tsx +0 -489
  241. package/src/components/BackupCodesForm.tsx +0 -287
  242. package/src/components/CategoryMenu.tsx +0 -155
  243. package/src/components/ChangePasswordForm.tsx +0 -273
  244. package/src/components/ConfirmationDialog.tsx +0 -48
  245. package/src/components/CurrencyCodeSelector.tsx +0 -60
  246. package/src/components/CurrencyInput.tsx +0 -80
  247. package/src/components/DashboardPage.tsx +0 -24
  248. package/src/components/DropdownMenu.tsx +0 -105
  249. package/src/components/ExpirationSecondsSelector.tsx +0 -60
  250. package/src/components/FakeEmailAdminPanel.tsx +0 -374
  251. package/src/components/Flag.tsx +0 -52
  252. package/src/components/ForgotPasswordForm.tsx +0 -174
  253. package/src/components/LoginForm.tsx +0 -468
  254. package/src/components/LogoutPage.tsx +0 -21
  255. package/src/components/RegisterForm.tsx +0 -1169
  256. package/src/components/ResetPasswordForm.tsx +0 -247
  257. package/src/components/SideMenu.tsx +0 -46
  258. package/src/components/SideMenuListItem.tsx +0 -75
  259. package/src/components/T.tsx +0 -24
  260. package/src/components/TDiv.tsx +0 -26
  261. package/src/components/TImgAlt.tsx +0 -28
  262. package/src/components/TopMenu.tsx +0 -252
  263. package/src/components/TotpSetupForm.tsx +0 -188
  264. package/src/components/TotpVerificationForm.tsx +0 -148
  265. package/src/components/TranslatedTitle.tsx +0 -31
  266. package/src/components/UserLanguageSelector.tsx +0 -45
  267. package/src/components/UserMenu.tsx +0 -15
  268. package/src/components/UserSettingsForm.tsx +0 -904
  269. package/src/components/VerifyEmailPage.tsx +0 -222
  270. package/src/components/__tests__/RegisterForm.totp.spec.tsx +0 -377
  271. package/src/components/__tests__/TotpSetupForm.spec.tsx +0 -258
  272. package/src/components/__tests__/TotpVerificationForm.spec.tsx +0 -259
  273. package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +0 -396
  274. package/src/contexts/AuthProvider.spec.tsx +0 -1284
  275. package/src/contexts/AuthProvider.tsx +0 -977
  276. package/src/contexts/I18nProvider.tsx +0 -164
  277. package/src/contexts/MenuContext.tsx +0 -426
  278. package/src/contexts/SuiteConfigProvider.tsx +0 -93
  279. package/src/contexts/ThemeProvider.tsx +0 -67
  280. package/src/hooks/useAuthenticatedApi.tsx +0 -67
  281. package/src/hooks/useBackupCodes.ts +0 -105
  282. package/src/hooks/useEmailVerification.ts +0 -54
  283. package/src/hooks/useExpiringValue.ts +0 -78
  284. package/src/hooks/useLocalStorage.ts +0 -18
  285. package/src/hooks/useUserSettings.ts +0 -271
  286. package/src/interfaces/IAppConfig.ts +0 -7
  287. package/src/interfaces/ICategoryConfig.ts +0 -41
  288. package/src/interfaces/IMenuConfig.ts +0 -28
  289. package/src/interfaces/IMenuOption.ts +0 -79
  290. package/src/services/__mocks__/authService.ts +0 -14
  291. package/src/services/__tests__/authService.totp.property.spec.ts +0 -360
  292. package/src/services/__tests__/authService.totp.spec.ts +0 -439
  293. package/src/services/api.ts +0 -13
  294. package/src/services/authService.ts +0 -692
  295. package/src/services/authenticatedApi.ts +0 -17
  296. package/src/services/index.ts +0 -3
  297. package/src/types/MenuType.ts +0 -15
  298. package/src/types/expirationSeconds.ts +0 -18
  299. package/src/types/index.ts +0 -1
  300. package/src/types/translation.ts +0 -20
  301. package/src/wrappers/BackupCodeLoginWrapper.tsx +0 -34
  302. package/src/wrappers/BackupCodesWrapper.tsx +0 -28
  303. package/src/wrappers/ChangePasswordFormWrapper.tsx +0 -34
  304. package/src/wrappers/ForgotPasswordFormWrapper.tsx +0 -45
  305. package/src/wrappers/LoginFormWrapper.tsx +0 -108
  306. package/src/wrappers/LogoutPageWrapper.tsx +0 -30
  307. package/src/wrappers/RegisterFormWrapper.tsx +0 -109
  308. package/src/wrappers/ResetPasswordFormWrapper.tsx +0 -57
  309. package/src/wrappers/UserSettingsFormWrapper.tsx +0 -140
  310. package/src/wrappers/VerifyEmailPageWrapper.tsx +0 -27
  311. 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
- });