Show error upon wrong password on Export screen (#594)

* add spinner to forget button

* error and test on export with wrong password

* reset error when typing and test
This commit is contained in:
Thibaut Sardan 2020-12-18 11:12:15 +01:00 committed by GitHub
parent 5af3be606e
commit 4d6242965a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 11 deletions

View File

@ -51,6 +51,39 @@ describe('Export component', () => {
expect(wrapper.find(Button).prop('isDisabled')).toBe(true);
});
it('shows an error if the password is wrong', async () => {
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(messaging, 'exportAccount').mockImplementation(async () => {
throw new Error('Unable to decode using the supplied passphrase');
});
enterPassword();
wrapper.find('[data-export-button] button').simulate('click');
await act(flushAllPromises);
wrapper.update();
// the first message is "You are exporting your account. Keep it safe and don't share it with anyone."
expect(wrapper.find('.warning-message').at(1).text()).toBe('Unable to decode using the supplied passphrase');
expect(wrapper.find(Button).prop('isDisabled')).toBe(true);
expect(wrapper.find('InputWithLabel').first().prop('isError')).toBe(true);
});
it('shows no error when typing again after a wrong password', async () => {
// eslint-disable-next-line @typescript-eslint/require-await
jest.spyOn(messaging, 'exportAccount').mockImplementation(async () => {
throw new Error('Unable to decode using the supplied passphrase');
});
enterPassword();
wrapper.find('[data-export-button] button').simulate('click');
await act(flushAllPromises);
wrapper.update();
enterPassword();
// the first message is "You are exporting your account. Keep it safe and don't share it with anyone."
expect(wrapper.find('.warning-message')).toHaveLength(1);
expect(wrapper.find(Button).prop('isDisabled')).toBe(false);
expect(wrapper.find('InputWithLabel').first().prop('isError')).toBe(false);
});
it('button is enabled after password is typed', async () => {
enterPassword();
await act(flushAllPromises);

View File

@ -23,13 +23,20 @@ function Export ({ className, match: { params: { address } } }: Props): React.Re
const onAction = useContext(ActionContext);
const [isBusy, setIsBusy] = useState(false);
const [pass, setPass] = useState('');
const [wrongPasswordHighlight, setWrongPasswordHighlight] = useState(false);
const [error, setError] = useState('');
const _goHome = useCallback(
() => onAction('/'),
[onAction]
);
const onPassChange = useCallback(
(password: string) => {
setPass(password);
setError('');
}
, []);
const _onExportButtonClick = useCallback(
(): void => {
setIsBusy(true);
@ -47,10 +54,8 @@ function Export ({ className, match: { params: { address } } }: Props): React.Re
})
.catch((error: Error) => {
console.error(error);
setError(error.message);
setIsBusy(false);
setWrongPasswordHighlight(true);
setTimeout(() => setWrongPasswordHighlight(false), 100);
});
},
[address, onAction, pass]
@ -71,17 +76,25 @@ function Export ({ className, match: { params: { address } } }: Props): React.Re
<InputWithLabel
data-export-password
disabled={isBusy}
isError={pass.length < MIN_LENGTH || wrongPasswordHighlight}
isError={pass.length < MIN_LENGTH || !!error}
label={t<string>('password for this account')}
onChange={setPass}
onChange={onPassChange}
type='password'
/>
{error && (
<Warning
isBelowInput
isDanger
>
{error}
</Warning>
)}
<Button
className='export-button'
data-export-button
isBusy={isBusy}
isDanger
isDisabled={pass.length === 0}
isDisabled={pass.length === 0 || !!error}
onClick={_onExportButtonClick}
>
{t<string>('I want to export this account')}
@ -109,6 +122,10 @@ export default withRouter(styled(Export)`
margin: auto;
}
.export-button {
margin-top: 6px;
}
.movedWarning {
margin-top: 8px;
}

View File

@ -3,7 +3,7 @@
import type { ThemeProps } from '../types';
import React, { useCallback, useContext } from 'react';
import React, { useCallback, useContext, useState } from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import styled from 'styled-components';
@ -19,6 +19,7 @@ interface Props extends RouteComponentProps<{ address: string }>, ThemeProps {
function Forget ({ className, match: { params: { address } } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const onAction = useContext(ActionContext);
const [isBusy, setIsBusy] = useState(false);
const _goHome = useCallback(
() => onAction('/'),
@ -26,10 +27,18 @@ function Forget ({ className, match: { params: { address } } }: Props): React.Re
);
const _onClick = useCallback(
(): Promise<void> =>
(): void => {
setIsBusy(true);
forgetAccount(address)
.then(() => onAction('/'))
.catch((error: Error) => console.error(error)),
.then(() => {
setIsBusy(false);
onAction('/');
})
.catch((error: Error) => {
setIsBusy(false);
console.error(error);
});
},
[address, onAction]
);
@ -46,6 +55,7 @@ function Forget ({ className, match: { params: { address } } }: Props): React.Re
</Warning>
<div className='actionArea'>
<Button
isBusy={isBusy}
isDanger
onClick={_onClick}
>