mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Refactor: Extract SRP input from create vault component (#13720)
This is a pure refactor that extracts the SRP input from the `CreateNewVault` component. This is intended to make future changes to the SRP input easier, and to reduce duplication between the old and new onboarding flows. Extensive unit tests have been added for the new SRP input component. A new test library was added (`@testing-library/user-event`) for simulating user events with components rendered using the `@testing-library` library. A new helper method has been added (`renderWithLocalization`) for rendering components using `@testing-library` with just our localization contexts added as a wrapper. The localization contexts were already added by the `renderWithProviders` helper function, but there is no need for a Redux provider in these unit tests.
This commit is contained in:
parent
219f8e6299
commit
f49e5076f3
@ -262,6 +262,7 @@
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/react": "^10.4.8",
|
||||
"@testing-library/react-hooks": "^3.2.1",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"addons-linter": "1.14.0",
|
||||
"babelify": "^10.0.0",
|
||||
|
@ -79,3 +79,11 @@ if (!window.crypto.getRandomValues) {
|
||||
// eslint-disable-next-line node/global-require
|
||||
window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues');
|
||||
}
|
||||
|
||||
// Used to test `clearClipboard` function
|
||||
if (!window.navigator.clipboard) {
|
||||
window.navigator.clipboard = {};
|
||||
}
|
||||
if (!window.navigator.clipboard.writeText) {
|
||||
window.navigator.clipboard.writeText = () => undefined;
|
||||
}
|
||||
|
@ -95,3 +95,17 @@ export function renderWithProvider(component, store) {
|
||||
|
||||
return render(component, { wrapper: Wrapper });
|
||||
}
|
||||
|
||||
export function renderWithLocalization(component) {
|
||||
const Wrapper = ({ children }) => (
|
||||
<I18nProvider currentLocale="en" current={en} en={en}>
|
||||
<LegacyI18nProvider>{children}</LegacyI18nProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
Wrapper.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
return render(component, { wrapper: Wrapper });
|
||||
}
|
||||
|
@ -54,6 +54,7 @@
|
||||
@import 'selected-account/index';
|
||||
@import 'signature-request/index';
|
||||
@import 'signature-request-original/index';
|
||||
@import 'srp-input/srp-input';
|
||||
@import 'tab-bar/index';
|
||||
@import 'token-cell/token-cell';
|
||||
@import 'token-list-display/token-list-display';
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { ethers } from 'ethers';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
import TextField from '../../ui/text-field';
|
||||
import Button from '../../ui/button';
|
||||
import { clearClipboard } from '../../../helpers/utils/util';
|
||||
import CheckBox from '../../ui/check-box';
|
||||
import Typography from '../../ui/typography';
|
||||
import { COLORS } from '../../../helpers/constants/design-system';
|
||||
import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase';
|
||||
|
||||
const { isValidMnemonic } = ethers.utils;
|
||||
import SrpInput from '../srp-input';
|
||||
|
||||
export default function CreateNewVault({
|
||||
disabled = false,
|
||||
@ -24,33 +19,11 @@ export default function CreateNewVault({
|
||||
const [password, setPassword] = useState('');
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [seedPhrase, setSeedPhrase] = useState('');
|
||||
const [seedPhraseError, setSeedPhraseError] = useState('');
|
||||
const [showSeedPhrase, setShowSeedPhrase] = useState(false);
|
||||
const [termsChecked, setTermsChecked] = useState(false);
|
||||
|
||||
const t = useI18nContext();
|
||||
const metricsEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const onSeedPhraseChange = useCallback(
|
||||
(rawSeedPhrase) => {
|
||||
let newSeedPhraseError = '';
|
||||
|
||||
if (rawSeedPhrase) {
|
||||
const parsedSeedPhrase = parseSecretRecoveryPhrase(rawSeedPhrase);
|
||||
const wordCount = parsedSeedPhrase.split(/\s/u).length;
|
||||
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
|
||||
newSeedPhraseError = t('seedPhraseReq');
|
||||
} else if (!isValidMnemonic(parsedSeedPhrase)) {
|
||||
newSeedPhraseError = t('invalidSeedPhrase');
|
||||
}
|
||||
}
|
||||
|
||||
setSeedPhrase(rawSeedPhrase);
|
||||
setSeedPhraseError(newSeedPhraseError);
|
||||
},
|
||||
[setSeedPhrase, setSeedPhraseError, t],
|
||||
);
|
||||
|
||||
const onPasswordChange = useCallback(
|
||||
(newPassword) => {
|
||||
let newConfirmPasswordError = '';
|
||||
@ -93,8 +66,7 @@ export default function CreateNewVault({
|
||||
seedPhrase &&
|
||||
(!includeTerms || termsChecked) &&
|
||||
!passwordError &&
|
||||
!confirmPasswordError &&
|
||||
!seedPhraseError;
|
||||
!confirmPasswordError;
|
||||
|
||||
const onImport = useCallback(
|
||||
async (event) => {
|
||||
@ -104,7 +76,7 @@ export default function CreateNewVault({
|
||||
return;
|
||||
}
|
||||
|
||||
await onSubmit(password, parseSecretRecoveryPhrase(seedPhrase));
|
||||
await onSubmit(password, seedPhrase);
|
||||
},
|
||||
[isValid, onSubmit, password, seedPhrase],
|
||||
);
|
||||
@ -121,10 +93,6 @@ export default function CreateNewVault({
|
||||
setTermsChecked((currentTermsChecked) => !currentTermsChecked);
|
||||
}, [metricsEvent]);
|
||||
|
||||
const toggleShowSeedPhrase = useCallback(() => {
|
||||
setShowSeedPhrase((currentShowSeedPhrase) => !currentShowSeedPhrase);
|
||||
}, []);
|
||||
|
||||
const termsOfUse = t('acceptTermsOfUse', [
|
||||
<a
|
||||
className="create-new-vault__terms-link"
|
||||
@ -140,56 +108,7 @@ export default function CreateNewVault({
|
||||
return (
|
||||
<form className="create-new-vault__form" onSubmit={onImport}>
|
||||
<div className="create-new-vault__srp-section">
|
||||
<label
|
||||
htmlFor="create-new-vault__srp"
|
||||
className="create-new-vault__srp-label"
|
||||
>
|
||||
<Typography>{t('secretRecoveryPhrase')}</Typography>
|
||||
</label>
|
||||
{showSeedPhrase ? (
|
||||
<textarea
|
||||
id="create-new-vault__srp"
|
||||
className="create-new-vault__srp-shown"
|
||||
onChange={(e) => onSeedPhraseChange(e.target.value)}
|
||||
onPaste={clearClipboard}
|
||||
value={seedPhrase}
|
||||
placeholder={t('seedPhrasePlaceholder')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
id="create-new-vault__srp"
|
||||
type="password"
|
||||
onChange={(e) => onSeedPhraseChange(e.target.value)}
|
||||
value={seedPhrase}
|
||||
placeholder={t('seedPhrasePlaceholderPaste')}
|
||||
autoComplete="off"
|
||||
onPaste={clearClipboard}
|
||||
/>
|
||||
)}
|
||||
{seedPhraseError ? (
|
||||
<Typography
|
||||
color={COLORS.ERROR1}
|
||||
tag="span"
|
||||
className="create-new-vault__srp-error"
|
||||
>
|
||||
{seedPhraseError}
|
||||
</Typography>
|
||||
) : null}
|
||||
<div className="create-new-vault__show-srp">
|
||||
<CheckBox
|
||||
id="create-new-vault__show-srp-checkbox"
|
||||
checked={showSeedPhrase}
|
||||
onClick={toggleShowSeedPhrase}
|
||||
title={t('showSeedPhrase')}
|
||||
/>
|
||||
<label
|
||||
className="create-new-vault__show-srp-label"
|
||||
htmlFor="create-new-vault__show-srp-checkbox"
|
||||
>
|
||||
<Typography tag="span">{t('showSeedPhrase')}</Typography>
|
||||
</label>
|
||||
</div>
|
||||
<SrpInput onChange={setSeedPhrase} />
|
||||
</div>
|
||||
<TextField
|
||||
id="password"
|
||||
|
@ -5,34 +5,11 @@
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
&__srp-shown {
|
||||
@include Paragraph;
|
||||
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
&__srp-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__srp-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__srp-error {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&__show-srp {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__show-srp-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&__terms {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
1
ui/components/app/srp-input/index.js
Normal file
1
ui/components/app/srp-input/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './srp-input';
|
112
ui/components/app/srp-input/srp-input.js
Normal file
112
ui/components/app/srp-input/srp-input.js
Normal file
@ -0,0 +1,112 @@
|
||||
import { ethers } from 'ethers';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import TextField from '../../ui/text-field';
|
||||
import { clearClipboard } from '../../../helpers/utils/util';
|
||||
import CheckBox from '../../ui/check-box';
|
||||
import Typography from '../../ui/typography';
|
||||
import { COLORS } from '../../../helpers/constants/design-system';
|
||||
import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase';
|
||||
|
||||
const { isValidMnemonic } = ethers.utils;
|
||||
|
||||
export default function SrpInput({ onChange }) {
|
||||
const [srpError, setSrpError] = useState('');
|
||||
const [draftSrp, setDraftSrp] = useState('');
|
||||
const [showSrp, setShowSrp] = useState(false);
|
||||
|
||||
const t = useI18nContext();
|
||||
|
||||
const onSrpChange = useCallback(
|
||||
(event) => {
|
||||
const rawSrp = event.target.value;
|
||||
let newSrpError = '';
|
||||
const parsedSeedPhrase = parseSecretRecoveryPhrase(rawSrp);
|
||||
|
||||
if (rawSrp) {
|
||||
const wordCount = parsedSeedPhrase.split(/\s/u).length;
|
||||
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
|
||||
newSrpError = t('seedPhraseReq');
|
||||
} else if (!isValidMnemonic(parsedSeedPhrase)) {
|
||||
newSrpError = t('invalidSeedPhrase');
|
||||
}
|
||||
}
|
||||
|
||||
setDraftSrp(rawSrp);
|
||||
setSrpError(newSrpError);
|
||||
onChange(newSrpError ? '' : parsedSeedPhrase);
|
||||
},
|
||||
[setDraftSrp, setSrpError, t, onChange],
|
||||
);
|
||||
|
||||
const toggleShowSrp = useCallback(() => {
|
||||
setShowSrp((currentShowSrp) => !currentShowSrp);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor="import-srp__srp" className="import-srp__srp-label">
|
||||
<Typography>{t('secretRecoveryPhrase')}</Typography>
|
||||
</label>
|
||||
{showSrp ? (
|
||||
<textarea
|
||||
id="import-srp__srp"
|
||||
className="import-srp__srp-shown"
|
||||
onChange={onSrpChange}
|
||||
onPaste={clearClipboard}
|
||||
value={draftSrp}
|
||||
placeholder={t('seedPhrasePlaceholder')}
|
||||
autoComplete="off"
|
||||
/>
|
||||
) : (
|
||||
<TextField
|
||||
id="import-srp__srp"
|
||||
type="password"
|
||||
onChange={onSrpChange}
|
||||
value={draftSrp}
|
||||
placeholder={t('seedPhrasePlaceholderPaste')}
|
||||
autoComplete="off"
|
||||
onPaste={clearClipboard}
|
||||
/>
|
||||
)}
|
||||
{srpError ? (
|
||||
<Typography
|
||||
color={COLORS.ERROR1}
|
||||
tag="span"
|
||||
className="import-srp__srp-error"
|
||||
>
|
||||
{srpError}
|
||||
</Typography>
|
||||
) : null}
|
||||
<div className="import-srp__show-srp">
|
||||
<CheckBox
|
||||
id="import-srp__show-srp-checkbox"
|
||||
checked={showSrp}
|
||||
onClick={toggleShowSrp}
|
||||
title={t('showSeedPhrase')}
|
||||
/>
|
||||
<label
|
||||
className="import-srp__show-srp-label"
|
||||
htmlFor="import-srp__show-srp-checkbox"
|
||||
>
|
||||
<Typography tag="span">{t('showSeedPhrase')}</Typography>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
SrpInput.propTypes = {
|
||||
/**
|
||||
* Event handler for SRP changes.
|
||||
*
|
||||
* This is only called with a valid, well-formated (i.e. exactly one space
|
||||
* between each word) SRP or with an empty string.
|
||||
*
|
||||
* This is called each time the draft SRP is updated. If the draft SRP is
|
||||
* valid, this is called with a well-formatted version of that draft SRP.
|
||||
* Otherwise, this is called with an empty string.
|
||||
*/
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
24
ui/components/app/srp-input/srp-input.scss
Normal file
24
ui/components/app/srp-input/srp-input.scss
Normal file
@ -0,0 +1,24 @@
|
||||
.import-srp {
|
||||
&__srp-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__srp-shown {
|
||||
@include Paragraph;
|
||||
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
&__srp-error {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&__show-srp {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__show-srp-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
23
ui/components/app/srp-input/srp-input.stories.js
Normal file
23
ui/components/app/srp-input/srp-input.stories.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import SrpInput from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/SrpInput',
|
||||
id: __filename,
|
||||
argTypes: {
|
||||
onChange: { action: 'changed' },
|
||||
},
|
||||
component: SrpInput,
|
||||
};
|
||||
|
||||
const Template = (args) => {
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
<SrpInput {...args} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultStory = Template.bind({});
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
571
ui/components/app/srp-input/srp-input.test.js
Normal file
571
ui/components/app/srp-input/srp-input.test.js
Normal file
@ -0,0 +1,571 @@
|
||||
import React from 'react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import enLocale from '../../../../app/_locales/en/messages.json';
|
||||
import { renderWithLocalization } from '../../../../test/lib/render-helpers';
|
||||
import SrpInput from '.';
|
||||
|
||||
const tooFewWords = new Array(11).fill('test').join(' ');
|
||||
const tooManyWords = new Array(25).fill('test').join(' ');
|
||||
const invalidWordCount = new Array(13).fill('test').join(' ');
|
||||
const invalidChecksum = new Array(12).fill('test').join(' ');
|
||||
const invalidWordCorrectChecksum = `aardvark ${new Array(10)
|
||||
.fill('test')
|
||||
.join(' ')} wolf`;
|
||||
const correct = `${new Array(11).fill('test').join(' ')} ball`;
|
||||
|
||||
const invalidInputs = [
|
||||
' ',
|
||||
'foo',
|
||||
'🙂',
|
||||
tooFewWords,
|
||||
tooManyWords,
|
||||
invalidWordCount,
|
||||
invalidChecksum,
|
||||
invalidWordCorrectChecksum,
|
||||
];
|
||||
|
||||
const poorlyFormattedInputs = [
|
||||
` ${correct}`,
|
||||
`\t${correct}`,
|
||||
`\n${correct}`,
|
||||
`${correct} `,
|
||||
`${correct}\t`,
|
||||
`${correct}\n`,
|
||||
`${new Array(11).fill('test').join(' ')} ball`,
|
||||
`${new Array(11).fill('test').join('\t')}\tball`,
|
||||
];
|
||||
|
||||
describe('srp-input', () => {
|
||||
describe('onChange event', () => {
|
||||
it('should not fire event on render', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
await waitFor(() => getByLabelText(enLocale.showSeedPhrase.message));
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('invalid typed inputs', () => {
|
||||
for (const invalidInput of invalidInputs) {
|
||||
it(`should fire event with empty string upon invalid input: '${invalidInput}'`, async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(invalidInput);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('invalid pasted inputs', () => {
|
||||
for (const invalidInput of invalidInputs) {
|
||||
it(`should fire event with empty string upon invalid pasted input: '${invalidInput}'`, async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
invalidInput,
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('valid typed inputs', () => {
|
||||
it('should fire event with a valid SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(correct);
|
||||
});
|
||||
|
||||
for (const poorlyFormattedInput of poorlyFormattedInputs) {
|
||||
it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(poorlyFormattedInput);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(correct);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('valid pasted inputs', () => {
|
||||
it('should fire event with a valid SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(correct);
|
||||
});
|
||||
|
||||
for (const poorlyFormattedInput of poorlyFormattedInputs) {
|
||||
it(`should fire with formatted SRP when given poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
poorlyFormattedInput,
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(correct);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('error message', () => {
|
||||
it('should not show error for empty input', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
await waitFor(() => getByLabelText(enLocale.showSeedPhrase.message));
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
describe('typed', () => {
|
||||
it('should show word requirement error if SRP has too few words', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(tooFewWords);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show word requirement error if SRP has too many words', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(tooManyWords);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(invalidWordCount);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show invalid SRP error if SRP is correct length but has an invalid checksum', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(invalidChecksum);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(invalidWordCorrectChecksum);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not show error for valid SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
for (const poorlyFormattedInput of poorlyFormattedInputs) {
|
||||
it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(poorlyFormattedInput);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('pasted', () => {
|
||||
it('should show word requirement error if SRP has too few words', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
tooFewWords,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show word requirement error if SRP has too many words', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
tooManyWords,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show word requirement error if SRP has an unsupported word count above 12 but below 24', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
invalidWordCount,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).not.toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
it('should show invalid SRP error if SRP is correct length but has an invalid checksum', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
invalidChecksum,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show invalid SRP error if SRP is correct length and has correct checksum but has an invalid word', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
invalidWordCorrectChecksum,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not show error for valid SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
|
||||
for (const poorlyFormattedInput of poorlyFormattedInputs) {
|
||||
it(`should not show error for poorly formatted valid SRP: '${poorlyFormattedInput}'`, () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
poorlyFormattedInput,
|
||||
);
|
||||
|
||||
expect(queryByText(enLocale.seedPhraseReq.message)).toBeNull();
|
||||
expect(queryByText(enLocale.invalidSeedPhrase.message)).toBeNull();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Show/hide SRP', () => {
|
||||
it('should default to not showing SRP', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, getByRole } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
).toHaveAttribute('type', 'password');
|
||||
expect(getByRole('checkbox')).not.toBeChecked();
|
||||
});
|
||||
|
||||
describe('default hidden', () => {
|
||||
it('should prevent reading typed SRP', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(queryByText(correct)).toBeNull();
|
||||
});
|
||||
|
||||
it('should prevent reading pasted SRP', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByLabelText, queryByText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(queryByText(correct)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shown then hidden', () => {
|
||||
it('should prevent reading typed SRP', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
|
||||
expect(queryByText(correct)).toBeNull();
|
||||
});
|
||||
|
||||
it('should prevent reading pasted SRP', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
|
||||
expect(queryByText(correct)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shown after input', () => {
|
||||
it('should show typed SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
|
||||
expect(queryByText(correct)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show pasted SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
|
||||
expect(queryByText(correct)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shown before input', () => {
|
||||
it('should show typed SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(queryByText(correct)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show pasted SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const {
|
||||
getByLabelText,
|
||||
getByRole,
|
||||
queryByText,
|
||||
} = renderWithLocalization(<SrpInput onChange={onChange} />);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(queryByText(correct)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear after paste', () => {
|
||||
it('should not clear clipboard after typing hidden SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText');
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(writeTextSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not clear clipboard after typing shown SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText');
|
||||
|
||||
const { getByLabelText, getByRole } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message).focus();
|
||||
userEvent.keyboard(correct);
|
||||
|
||||
expect(writeTextSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should clear the clipboard after pasting hidden SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText');
|
||||
|
||||
const { getByLabelText } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(writeTextSpy).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
it('should clear the clipboard after pasting shown SRP', () => {
|
||||
const onChange = jest.fn();
|
||||
const writeTextSpy = jest.spyOn(window.navigator.clipboard, 'writeText');
|
||||
|
||||
const { getByLabelText, getByRole } = renderWithLocalization(
|
||||
<SrpInput onChange={onChange} />,
|
||||
);
|
||||
userEvent.click(getByRole('checkbox'));
|
||||
userEvent.paste(
|
||||
getByLabelText(enLocale.secretRecoveryPhrase.message),
|
||||
correct,
|
||||
);
|
||||
|
||||
expect(writeTextSpy).toHaveBeenCalledWith('');
|
||||
});
|
||||
});
|
||||
});
|
@ -4091,6 +4091,13 @@
|
||||
"@babel/runtime" "^7.10.3"
|
||||
"@testing-library/dom" "^7.17.1"
|
||||
|
||||
"@testing-library/user-event@^13.5.0":
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295"
|
||||
integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
|
Loading…
Reference in New Issue
Block a user