diff --git a/src/components/CreateWallet.test.tsx b/src/components/CreateWallet.test.tsx index 6330deea..49c0eafe 100644 --- a/src/components/CreateWallet.test.tsx +++ b/src/components/CreateWallet.test.tsx @@ -19,7 +19,8 @@ jest.mock('../libs/JmWalletApi', () => ({ const NOOP = () => {} describe('', () => { - const testWalletName = 'wallet' + const testWalletName = 'test_wallet21' + const invalidTestWalletName = 'invalid_wallet_name!' const testWalletPassword = 'correct horse battery staple' const setup = ({ @@ -56,12 +57,14 @@ describe('', () => { it('should show validation messages to user if form is invalid', async () => { act(() => setup({})) + expect(await screen.queryByText('create_wallet.feedback_invalid_wallet_name')).not.toBeInTheDocument() + expect(await screen.queryByText('create_wallet.feedback_invalid_password')).not.toBeInTheDocument() + expect(await screen.queryByText('create_wallet.feedback_invalid_password_confirm')).not.toBeInTheDocument() expect(await screen.findByText('create_wallet.button_create')).toBeVisible() act(() => { // click on the "create" button without filling the form - const createWalletButton = screen.getByText('create_wallet.button_create') - user.click(createWalletButton) + user.click(screen.getByText('create_wallet.button_create')) }) expect(await screen.findByText('create_wallet.feedback_invalid_wallet_name')).toBeVisible() @@ -69,11 +72,32 @@ describe('', () => { expect(await screen.findByText('create_wallet.feedback_invalid_password_confirm')).toBeVisible() }) + it('should not submit form if wallet name contains invalid characters', async () => { + act(() => setup({})) + + expect(await screen.queryByText('create_wallet.feedback_invalid_wallet_name')).not.toBeInTheDocument() + expect(await screen.queryByText('create_wallet.feedback_invalid_password_confirm')).not.toBeInTheDocument() + + act(() => { + user.type(screen.getByPlaceholderText('create_wallet.placeholder_wallet_name'), invalidTestWalletName) + user.type(screen.getByPlaceholderText('create_wallet.placeholder_password'), testWalletPassword) + user.type(screen.getByPlaceholderText('create_wallet.placeholder_password_confirm'), testWalletPassword) + }) + + act(() => user.click(screen.getByText('create_wallet.button_create'))) + + expect(await screen.findByText('create_wallet.button_create')).toBeVisible() + expect(await screen.findByText('create_wallet.feedback_invalid_wallet_name')).toBeVisible() + expect(await screen.queryByText('create_wallet.feedback_invalid_password_confirm')).not.toBeInTheDocument() + }) + it('should not submit form if passwords do not match', async () => { act(() => setup({})) expect(await screen.findByPlaceholderText('create_wallet.placeholder_password')).toBeVisible() expect(await screen.findByPlaceholderText('create_wallet.placeholder_password_confirm')).toBeVisible() + expect(await screen.queryByText('create_wallet.feedback_invalid_wallet_name')).not.toBeInTheDocument() + expect(await screen.queryByText('create_wallet.feedback_invalid_password_confirm')).not.toBeInTheDocument() expect(await screen.findByText('create_wallet.button_create')).toBeVisible() act(() => { @@ -82,12 +106,10 @@ describe('', () => { user.type(screen.getByPlaceholderText('create_wallet.placeholder_password_confirm'), 'a_mismatching_input') }) - act(() => { - const createWalletButton = screen.getByText('create_wallet.button_create') - user.click(createWalletButton) - }) + act(() => user.click(screen.getByText('create_wallet.button_create'))) expect(await screen.findByText('create_wallet.button_create')).toBeVisible() + expect(await screen.queryByText('create_wallet.feedback_invalid_wallet_name')).not.toBeInTheDocument() expect(await screen.findByText('create_wallet.feedback_invalid_password_confirm')).toBeVisible() }) @@ -114,8 +136,7 @@ describe('', () => { }) await act(async () => { - const createWalletButton = screen.getByText('create_wallet.button_create') - user.click(createWalletButton) + user.click(screen.getByText('create_wallet.button_create')) await waitFor(() => screen.findByText(/create_wallet.button_creating/)) }) diff --git a/src/components/WalletCreationForm.tsx b/src/components/WalletCreationForm.tsx index baf34414..db7ba6e5 100644 --- a/src/components/WalletCreationForm.tsx +++ b/src/components/WalletCreationForm.tsx @@ -3,7 +3,7 @@ import * as rb from 'react-bootstrap' import { useTranslation } from 'react-i18next' import { Formik, FormikErrors } from 'formik' import Sprite from './Sprite' -import { sanitizeWalletName } from '../utils' +import { JM_WALLET_FILE_EXTENSION, sanitizeWalletName } from '../utils' import styles from './WalletCreationForm.module.css' export interface CreateWalletFormValues { @@ -20,6 +20,9 @@ const initialCreateWalletFormValues: CreateWalletFormValues = { export type WalletNameAndPassword = { name: string; password: string } +const MAX_WALLET_NAME_LENGTH = 240 - JM_WALLET_FILE_EXTENSION.length +const validateWalletName = (input: string) => input.length <= MAX_WALLET_NAME_LENGTH && /^[\w-]+$/.test(input) + interface WalletCreationFormProps { initialValues?: CreateWalletFormValues submitButtonText: (isSubmitting: boolean) => React.ReactNode | string @@ -38,7 +41,7 @@ const WalletCreationForm = ({ const validate = useCallback( (values: CreateWalletFormValues) => { const errors = {} as FormikErrors - if (!values.walletName) { + if (!values.walletName || !validateWalletName(values.walletName)) { errors.walletName = t('create_wallet.feedback_invalid_wallet_name') } if (!values.password) { @@ -75,6 +78,7 @@ const WalletCreationForm = ({ isValid={touched.walletName && !errors.walletName} isInvalid={touched.walletName && !!errors.walletName} className={styles.input} + maxLength={MAX_WALLET_NAME_LENGTH} /> {t('create_wallet.feedback_valid')} {errors.walletName} diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 4f50da28..f75448b5 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -125,7 +125,7 @@ "feedback_valid": "Looks good!", "label_wallet_name": "Wallet name", "placeholder_wallet_name": "Your Wallet...", - "feedback_invalid_wallet_name": "Please set a wallet name.", + "feedback_invalid_wallet_name": "Please choose a valid wallet name: Use only letters, numbers, underscores or hyphens.", "label_password": "Password to unlock the wallet", "placeholder_password": "Choose a secure password...", "feedback_invalid_password": "Please set a password.", diff --git a/src/utils.ts b/src/utils.ts index 9c68b985..30590cb8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,7 +13,7 @@ const SATS_FORMATTER = new Intl.NumberFormat('en-US', { export const BTC: Unit = 'BTC' export const SATS: Unit = 'sats' -const JM_WALLET_FILE_EXTENSION = '.jmdat' +export const JM_WALLET_FILE_EXTENSION = '.jmdat' export const DUMMY_MNEMONIC_PHRASE: MnemonicPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'.split(' ')