@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.
- package/package.json +5 -4
- package/src/auth/AdminRoute.d.ts +8 -0
- package/src/auth/AdminRoute.d.ts.map +1 -0
- package/src/auth/AdminRoute.js +28 -0
- package/src/auth/Private.d.ts +6 -0
- package/src/auth/Private.d.ts.map +1 -0
- package/src/auth/Private.js +14 -0
- package/src/auth/PrivateRoute.d.ts +8 -0
- package/src/auth/PrivateRoute.d.ts.map +1 -0
- package/src/auth/PrivateRoute.js +23 -0
- package/src/auth/UnAuth.d.ts +6 -0
- package/src/auth/UnAuth.d.ts.map +1 -0
- package/src/auth/UnAuth.js +14 -0
- package/src/auth/UnAuthRoute.d.ts +8 -0
- package/src/auth/UnAuthRoute.d.ts.map +1 -0
- package/src/auth/UnAuthRoute.js +22 -0
- package/src/auth/{index.ts → index.d.ts} +3 -2
- package/src/auth/index.d.ts.map +1 -0
- package/src/auth/index.js +12 -0
- package/src/components/ApiAccess.d.ts +16 -0
- package/src/components/ApiAccess.d.ts.map +1 -0
- package/src/components/ApiAccess.js +77 -0
- package/src/components/BackupCodeLoginForm.d.ts +43 -0
- package/src/components/BackupCodeLoginForm.d.ts.map +1 -0
- package/src/components/BackupCodeLoginForm.js +139 -0
- package/src/components/BackupCodesForm.d.ts +26 -0
- package/src/components/BackupCodesForm.d.ts.map +1 -0
- package/src/components/BackupCodesForm.js +120 -0
- package/src/components/CategoryMenu.d.ts +17 -0
- package/src/components/CategoryMenu.d.ts.map +1 -0
- package/src/components/CategoryMenu.js +65 -0
- package/src/components/ChangePasswordForm.d.ts +26 -0
- package/src/components/ChangePasswordForm.d.ts.map +1 -0
- package/src/components/ChangePasswordForm.js +78 -0
- package/src/components/ConfirmationDialog.d.ts +13 -0
- package/src/components/ConfirmationDialog.d.ts.map +1 -0
- package/src/components/ConfirmationDialog.js +10 -0
- package/src/components/CurrencyCodeSelector.d.ts +9 -0
- package/src/components/CurrencyCodeSelector.d.ts.map +1 -0
- package/src/components/CurrencyCodeSelector.js +31 -0
- package/src/components/CurrencyInput.d.ts +13 -0
- package/src/components/CurrencyInput.d.ts.map +1 -0
- package/src/components/CurrencyInput.js +22 -0
- package/src/components/DashboardPage.d.ts +8 -0
- package/src/components/DashboardPage.d.ts.map +1 -0
- package/src/components/DashboardPage.js +10 -0
- package/src/components/DropdownMenu.d.ts +13 -0
- package/src/components/DropdownMenu.d.ts.map +1 -0
- package/src/components/DropdownMenu.js +63 -0
- package/src/components/ExpirationSecondsSelector.d.ts +13 -0
- package/src/components/ExpirationSecondsSelector.d.ts.map +1 -0
- package/src/components/ExpirationSecondsSelector.js +32 -0
- package/src/components/FakeEmailAdminPanel.d.ts +23 -0
- package/src/components/FakeEmailAdminPanel.d.ts.map +1 -0
- package/src/components/FakeEmailAdminPanel.js +147 -0
- package/src/components/Flag.d.ts +20 -0
- package/src/components/Flag.d.ts.map +1 -0
- package/src/components/Flag.js +43 -0
- package/src/components/ForgotPasswordForm.d.ts +18 -0
- package/src/components/ForgotPasswordForm.d.ts.map +1 -0
- package/src/components/ForgotPasswordForm.js +61 -0
- package/src/components/LoginForm.d.ts +44 -0
- package/src/components/LoginForm.d.ts.map +1 -0
- package/src/components/LoginForm.js +123 -0
- package/src/components/LogoutPage.d.ts +8 -0
- package/src/components/LogoutPage.d.ts.map +1 -0
- package/src/components/LogoutPage.js +16 -0
- package/src/components/RegisterForm.d.ts +100 -0
- package/src/components/RegisterForm.d.ts.map +1 -0
- package/src/components/RegisterForm.js +306 -0
- package/src/components/ResetPasswordForm.d.ts +23 -0
- package/src/components/ResetPasswordForm.d.ts.map +1 -0
- package/src/components/ResetPasswordForm.js +78 -0
- package/src/components/SideMenu.d.ts +8 -0
- package/src/components/SideMenu.d.ts.map +1 -0
- package/src/components/SideMenu.js +25 -0
- package/src/components/SideMenuListItem.d.ts +13 -0
- package/src/components/SideMenuListItem.d.ts.map +1 -0
- package/src/components/SideMenuListItem.js +44 -0
- package/src/components/T.d.ts +7 -0
- package/src/components/T.d.ts.map +1 -0
- package/src/components/T.js +13 -0
- package/src/components/TDiv.d.ts +9 -0
- package/src/components/TDiv.d.ts.map +1 -0
- package/src/components/TDiv.js +14 -0
- package/src/components/TImgAlt.d.ts +10 -0
- package/src/components/TImgAlt.d.ts.map +1 -0
- package/src/components/TImgAlt.js +14 -0
- package/src/components/TopMenu.d.ts +50 -0
- package/src/components/TopMenu.d.ts.map +1 -0
- package/src/components/TopMenu.js +79 -0
- package/src/components/TotpSetupForm.d.ts +24 -0
- package/src/components/TotpSetupForm.d.ts.map +1 -0
- package/src/components/TotpSetupForm.js +82 -0
- package/src/components/TotpVerificationForm.d.ts +23 -0
- package/src/components/TotpVerificationForm.d.ts.map +1 -0
- package/src/components/TotpVerificationForm.js +66 -0
- package/src/components/TranslatedTitle.d.ts +8 -0
- package/src/components/TranslatedTitle.d.ts.map +1 -0
- package/src/components/TranslatedTitle.js +15 -0
- package/src/components/UserLanguageSelector.d.ts +4 -0
- package/src/components/UserLanguageSelector.d.ts.map +1 -0
- package/src/components/UserLanguageSelector.js +31 -0
- package/src/components/UserMenu.d.ts +4 -0
- package/src/components/UserMenu.d.ts.map +1 -0
- package/src/components/UserMenu.js +12 -0
- package/src/components/UserSettingsForm.d.ts +113 -0
- package/src/components/UserSettingsForm.d.ts.map +1 -0
- package/src/components/UserSettingsForm.js +269 -0
- package/src/components/VerifyEmailPage.d.ts +24 -0
- package/src/components/VerifyEmailPage.d.ts.map +1 -0
- package/src/components/VerifyEmailPage.js +81 -0
- package/src/components/{index.ts → index.d.ts} +1 -1
- package/src/components/index.d.ts.map +1 -0
- package/src/components/index.js +35 -0
- package/src/contexts/AuthProvider.d.ts +186 -0
- package/src/contexts/AuthProvider.d.ts.map +1 -0
- package/src/contexts/AuthProvider.js +515 -0
- package/src/contexts/I18nProvider.d.ts +27 -0
- package/src/contexts/I18nProvider.d.ts.map +1 -0
- package/src/contexts/I18nProvider.js +60 -0
- package/src/contexts/MenuContext.d.ts +28 -0
- package/src/contexts/MenuContext.d.ts.map +1 -0
- package/src/contexts/MenuContext.js +292 -0
- package/src/contexts/SuiteConfigProvider.d.ts +44 -0
- package/src/contexts/SuiteConfigProvider.d.ts.map +1 -0
- package/src/contexts/SuiteConfigProvider.js +43 -0
- package/src/contexts/ThemeProvider.d.ts +15 -0
- package/src/contexts/ThemeProvider.d.ts.map +1 -0
- package/src/contexts/ThemeProvider.js +36 -0
- package/src/contexts/{index.ts → index.d.ts} +1 -0
- package/src/contexts/index.d.ts.map +1 -0
- package/src/contexts/index.js +8 -0
- package/src/hooks/{index.ts → index.d.ts} +1 -0
- package/src/hooks/index.d.ts.map +1 -0
- package/src/hooks/index.js +9 -0
- package/src/hooks/useAuthenticatedApi.d.ts +34 -0
- package/src/hooks/useAuthenticatedApi.d.ts.map +1 -0
- package/src/hooks/useAuthenticatedApi.js +46 -0
- package/src/hooks/useBackupCodes.d.ts +15 -0
- package/src/hooks/useBackupCodes.d.ts.map +1 -0
- package/src/hooks/useBackupCodes.js +74 -0
- package/src/hooks/useEmailVerification.d.ts +10 -0
- package/src/hooks/useEmailVerification.d.ts.map +1 -0
- package/src/hooks/useEmailVerification.js +40 -0
- package/src/hooks/useExpiringValue.d.ts +14 -0
- package/src/hooks/useExpiringValue.d.ts.map +1 -0
- package/src/hooks/useExpiringValue.js +53 -0
- package/src/hooks/useLocalStorage.d.ts +2 -0
- package/src/hooks/useLocalStorage.d.ts.map +1 -0
- package/src/hooks/useLocalStorage.js +15 -0
- package/src/hooks/useUserSettings.d.ts +48 -0
- package/src/hooks/useUserSettings.d.ts.map +1 -0
- package/src/hooks/useUserSettings.js +171 -0
- package/src/{index.ts → index.d.ts} +1 -1
- package/src/index.d.ts.map +1 -0
- package/src/index.js +12 -0
- package/src/interfaces/IAppConfig.d.ts +8 -0
- package/src/interfaces/IAppConfig.d.ts.map +1 -0
- package/src/interfaces/IAppConfig.js +2 -0
- package/src/interfaces/ICategoryConfig.d.ts +41 -0
- package/src/interfaces/ICategoryConfig.d.ts.map +1 -0
- package/src/interfaces/ICategoryConfig.js +2 -0
- package/src/interfaces/IMenuConfig.d.ts +28 -0
- package/src/interfaces/IMenuConfig.d.ts.map +1 -0
- package/src/interfaces/IMenuConfig.js +2 -0
- package/src/interfaces/IMenuOption.d.ts +82 -0
- package/src/interfaces/IMenuOption.d.ts.map +1 -0
- package/src/interfaces/IMenuOption.js +2 -0
- package/src/interfaces/{index.ts → index.d.ts} +1 -0
- package/src/interfaces/index.d.ts.map +1 -0
- package/src/interfaces/index.js +7 -0
- package/src/services/__mocks__/authService.d.ts +21 -0
- package/src/services/__mocks__/authService.d.ts.map +1 -0
- package/src/services/__mocks__/authService.js +15 -0
- package/src/services/api.d.ts +3 -0
- package/src/services/api.d.ts.map +1 -0
- package/src/services/api.js +14 -0
- package/src/services/authService.d.ts +126 -0
- package/src/services/authService.d.ts.map +1 -0
- package/src/services/authService.js +471 -0
- package/src/services/authenticatedApi.d.ts +3 -0
- package/src/services/authenticatedApi.d.ts.map +1 -0
- package/src/services/authenticatedApi.js +18 -0
- package/src/services/index.d.ts +4 -0
- package/src/services/index.d.ts.map +1 -0
- package/src/services/index.js +6 -0
- package/src/types/MenuType.d.ts +11 -0
- package/src/types/MenuType.d.ts.map +1 -0
- package/src/types/MenuType.js +12 -0
- package/src/types/expirationSeconds.d.ts +3 -0
- package/src/types/expirationSeconds.d.ts.map +1 -0
- package/src/types/expirationSeconds.js +17 -0
- package/src/types/index.d.ts +2 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/index.js +4 -0
- package/src/types/translation.d.ts +10 -0
- package/src/types/translation.d.ts.map +1 -0
- package/src/types/translation.js +9 -0
- package/src/wrappers/BackupCodeLoginWrapper.d.ts +8 -0
- package/src/wrappers/BackupCodeLoginWrapper.d.ts.map +1 -0
- package/src/wrappers/BackupCodeLoginWrapper.js +20 -0
- package/src/wrappers/BackupCodesWrapper.d.ts +7 -0
- package/src/wrappers/BackupCodesWrapper.d.ts.map +1 -0
- package/src/wrappers/BackupCodesWrapper.js +17 -0
- package/src/wrappers/ChangePasswordFormWrapper.d.ts +8 -0
- package/src/wrappers/ChangePasswordFormWrapper.d.ts.map +1 -0
- package/src/wrappers/ChangePasswordFormWrapper.js +21 -0
- package/src/wrappers/ForgotPasswordFormWrapper.d.ts +8 -0
- package/src/wrappers/ForgotPasswordFormWrapper.d.ts.map +1 -0
- package/src/wrappers/ForgotPasswordFormWrapper.js +29 -0
- package/src/wrappers/LoginFormWrapper.d.ts +21 -0
- package/src/wrappers/LoginFormWrapper.d.ts.map +1 -0
- package/src/wrappers/LoginFormWrapper.js +72 -0
- package/src/wrappers/LogoutPageWrapper.d.ts +9 -0
- package/src/wrappers/LogoutPageWrapper.d.ts.map +1 -0
- package/src/wrappers/LogoutPageWrapper.js +21 -0
- package/src/wrappers/RegisterFormWrapper.d.ts +20 -0
- package/src/wrappers/RegisterFormWrapper.d.ts.map +1 -0
- package/src/wrappers/RegisterFormWrapper.js +51 -0
- package/src/wrappers/ResetPasswordFormWrapper.d.ts +9 -0
- package/src/wrappers/ResetPasswordFormWrapper.d.ts.map +1 -0
- package/src/wrappers/ResetPasswordFormWrapper.js +34 -0
- package/src/wrappers/UserSettingsFormWrapper.d.ts +29 -0
- package/src/wrappers/UserSettingsFormWrapper.d.ts.map +1 -0
- package/src/wrappers/UserSettingsFormWrapper.js +95 -0
- package/src/wrappers/VerifyEmailPageWrapper.d.ts +8 -0
- package/src/wrappers/VerifyEmailPageWrapper.d.ts.map +1 -0
- package/src/wrappers/VerifyEmailPageWrapper.js +21 -0
- package/src/wrappers/{index.tsx → index.d.ts} +1 -10
- package/src/wrappers/index.d.ts.map +1 -0
- package/src/wrappers/index.js +24 -0
- package/LICENSE +0 -21
- package/src/auth/AdminRoute.tsx +0 -54
- package/src/auth/Private.tsx +0 -17
- package/src/auth/PrivateRoute.tsx +0 -28
- package/src/auth/UnAuth.tsx +0 -16
- package/src/auth/UnAuthRoute.tsx +0 -30
- package/src/components/ApiAccess.tsx +0 -175
- package/src/components/BackupCodeLoginForm.tsx +0 -489
- package/src/components/BackupCodesForm.tsx +0 -287
- package/src/components/CategoryMenu.tsx +0 -155
- package/src/components/ChangePasswordForm.tsx +0 -273
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/CurrencyCodeSelector.tsx +0 -60
- package/src/components/CurrencyInput.tsx +0 -80
- package/src/components/DashboardPage.tsx +0 -24
- package/src/components/DropdownMenu.tsx +0 -105
- package/src/components/ExpirationSecondsSelector.tsx +0 -60
- package/src/components/FakeEmailAdminPanel.tsx +0 -374
- package/src/components/Flag.tsx +0 -52
- package/src/components/ForgotPasswordForm.tsx +0 -174
- package/src/components/LoginForm.tsx +0 -468
- package/src/components/LogoutPage.tsx +0 -21
- package/src/components/RegisterForm.tsx +0 -1169
- package/src/components/ResetPasswordForm.tsx +0 -247
- package/src/components/SideMenu.tsx +0 -46
- package/src/components/SideMenuListItem.tsx +0 -75
- package/src/components/T.tsx +0 -24
- package/src/components/TDiv.tsx +0 -26
- package/src/components/TImgAlt.tsx +0 -28
- package/src/components/TopMenu.tsx +0 -252
- package/src/components/TotpSetupForm.tsx +0 -188
- package/src/components/TotpVerificationForm.tsx +0 -148
- package/src/components/TranslatedTitle.tsx +0 -31
- package/src/components/UserLanguageSelector.tsx +0 -45
- package/src/components/UserMenu.tsx +0 -15
- package/src/components/UserSettingsForm.tsx +0 -904
- package/src/components/VerifyEmailPage.tsx +0 -222
- package/src/components/__tests__/RegisterForm.totp.spec.tsx +0 -377
- package/src/components/__tests__/TotpSetupForm.spec.tsx +0 -258
- package/src/components/__tests__/TotpVerificationForm.spec.tsx +0 -259
- package/src/components/__tests__/UserSettingsForm.totp.spec.tsx +0 -396
- package/src/contexts/AuthProvider.spec.tsx +0 -1284
- package/src/contexts/AuthProvider.tsx +0 -977
- package/src/contexts/I18nProvider.tsx +0 -164
- package/src/contexts/MenuContext.tsx +0 -426
- package/src/contexts/SuiteConfigProvider.tsx +0 -93
- package/src/contexts/ThemeProvider.tsx +0 -67
- package/src/hooks/useAuthenticatedApi.tsx +0 -67
- package/src/hooks/useBackupCodes.ts +0 -105
- package/src/hooks/useEmailVerification.ts +0 -54
- package/src/hooks/useExpiringValue.ts +0 -78
- package/src/hooks/useLocalStorage.ts +0 -18
- package/src/hooks/useUserSettings.ts +0 -271
- package/src/interfaces/IAppConfig.ts +0 -7
- package/src/interfaces/ICategoryConfig.ts +0 -41
- package/src/interfaces/IMenuConfig.ts +0 -28
- package/src/interfaces/IMenuOption.ts +0 -79
- package/src/services/__mocks__/authService.ts +0 -14
- package/src/services/__tests__/authService.totp.property.spec.ts +0 -360
- package/src/services/__tests__/authService.totp.spec.ts +0 -439
- package/src/services/api.ts +0 -13
- package/src/services/authService.ts +0 -692
- package/src/services/authenticatedApi.ts +0 -17
- package/src/services/index.ts +0 -3
- package/src/types/MenuType.ts +0 -15
- package/src/types/expirationSeconds.ts +0 -18
- package/src/types/index.ts +0 -1
- package/src/types/translation.ts +0 -20
- package/src/wrappers/BackupCodeLoginWrapper.tsx +0 -34
- package/src/wrappers/BackupCodesWrapper.tsx +0 -28
- package/src/wrappers/ChangePasswordFormWrapper.tsx +0 -34
- package/src/wrappers/ForgotPasswordFormWrapper.tsx +0 -45
- package/src/wrappers/LoginFormWrapper.tsx +0 -108
- package/src/wrappers/LogoutPageWrapper.tsx +0 -30
- package/src/wrappers/RegisterFormWrapper.tsx +0 -109
- package/src/wrappers/ResetPasswordFormWrapper.tsx +0 -57
- package/src/wrappers/UserSettingsFormWrapper.tsx +0 -140
- package/src/wrappers/VerifyEmailPageWrapper.tsx +0 -27
- 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
|
-
});
|