Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions packages/extension-ui/src/Popup/Accounts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import {
Link,
MediaContext,
AddAccount,
ButtonArea,
VerticalSpace
ButtonArea
} from '../../components';
import Account from './Account';
import styled from 'styled-components';
Expand Down Expand Up @@ -52,6 +51,11 @@ const QrButton = styled(Button)`
}
`;

const AccountsArea = styled.div`
height: 100%;
overflow: scroll;
`;

type Props = {};

export default function Accounts (): React.ReactElement<Props> {
Expand All @@ -74,14 +78,16 @@ export default function Accounts (): React.ReactElement<Props> {
>
You currently don&apos;t have any accounts. Either create a new account or if you have an existing account you wish to use, import it with the seed phrase.
</AddAccount>
: accounts.map((json, index): React.ReactNode => (
<Account
{...json}
key={`${index}:${json.address}`}
/>
))
: <AccountsArea>
{
accounts.map((json, index): React.ReactNode => (
<Account
{...json}
key={`${index}:${json.address}`}
/>))
}
</AccountsArea>
}
<VerticalSpace/>
<ButtonArea>
<ButtonWithSubtitle to='/account/create'>
<h4>Create New Account</h4>
Expand Down
22 changes: 13 additions & 9 deletions packages/extension-ui/src/Popup/CreateAccount/AccountName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.

import React, { useState } from 'react';
import { Address, Button } from '@polkadot/extension-ui/components';
import { Address, Button, ButtonArea, VerticalSpace } from '@polkadot/extension-ui/components';
import { Name, Password } from '@polkadot/extension-ui/partials';

interface Props {
Expand All @@ -22,15 +22,19 @@ function AccountName ({ onCreate, address }: Props): React.ReactElement<Props> {
/>
{name && <Password onChange={setPassword} />}
{name && password && (
<Address
address={address}
name={name}
>
<Button
label='Add the account with the generated seed'
onClick={(): void | Promise<void | boolean> => onCreate(name, password)}
<>
<Address
address={address}
name={name}
/>
</Address>
<VerticalSpace/>
<ButtonArea>
<Button
label='Add the account with the generated seed'
onClick={(): void | Promise<void | boolean> => onCreate(name, password)}
/>
</ButtonArea>
</>
)}
</>;
}
Expand Down
108 changes: 93 additions & 15 deletions packages/extension-ui/src/Popup/CreateAccount/CreateAccount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,46 @@
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';

import { History } from 'history';
import CreateAccount from '.';
import { configure, mount, ReactWrapper } from 'enzyme';
import { MemoryRouter } from 'react-router';
import * as messaging from '@polkadot/extension-ui/messaging';
import { act } from 'react-dom/test-utils';
import { flushAllPromises } from '@polkadot/extension-ui/testHelpers';
import { ActionText, Button, defaultTheme } from '@polkadot/extension-ui/components';
import {
ActionContext,
ActionText,
Button,
defaultTheme,
Input,
InputWithLabel
} from '@polkadot/extension-ui/components';
import CreationStep from '@polkadot/extension-ui/Popup/CreateAccount/CreationStep';
import { ThemeProvider } from 'styled-components';

configure({ adapter: new Adapter() });

describe('Create Account', () => {
let wrapper: ReactWrapper;
let historyMock: History;
let onActionStub: jest.Mock;
const exampleAccount = {
seed: 'horse battery staple correct',
address: 'HjoBp62cvsWDA3vtNMWxz6c9q13ReEHi9UGHK7JbZweH5g5'
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mountComponent = (): ReactWrapper => mount(<MemoryRouter initialEntries={['/account/create']} initialIndex={0}>
<ThemeProvider theme={defaultTheme}>
<CreateAccount history={historyMock}/>
</ThemeProvider>
<ActionContext.Provider value={onActionStub}>
<ThemeProvider theme={defaultTheme}>
<CreateAccount/>
</ThemeProvider>
</ActionContext.Provider>
</MemoryRouter>);

const check = (input: ReactWrapper): unknown => input.simulate('change', { target: { checked: true } });

beforeEach(async () => {
historyMock = {
push: jest.fn()
} as unknown as History;
onActionStub = jest.fn();
jest.spyOn(messaging, 'createSeed').mockResolvedValue(exampleAccount);
jest.spyOn(messaging, 'createAccountSuri').mockResolvedValue(true);
wrapper = mountComponent();
await act(flushAllPromises);
wrapper.update();
Expand All @@ -51,16 +59,86 @@ describe('Create Account', () => {
expect(wrapper.find(Button).prop('isDisabled')).toBe(true);
});

it('action text is "Cancel"', () => {
expect(wrapper.find(CreationStep).find(ActionText).text()).toBe('Cancel');
});

it('clicking "Cancel" redirects to main screen', () => {
wrapper.find(CreationStep).find(ActionText).simulate('click');
expect(onActionStub).toBeCalledWith('/');
});

it('clicking on Next activates phase 2', () => {
wrapper.find('input[type="checkbox"]').simulate('change', { target: { checked: true } });
check(wrapper.find('input[type="checkbox"]'));
wrapper.find('button').simulate('click');
expect(wrapper.find(CreationStep).text()).toBe('Create an account:2/2Cancel');
});
});

it('clicking cancel redirects to main screen', () => {
wrapper.find(CreationStep).find(ActionText).simulate('click');
const { push } = historyMock;
expect(push).lastCalledWith('/');
describe('Phase 2', () => {
const type = (input: ReactWrapper, value: string): unknown => input.simulate('change', { target: { value } });

beforeEach(() => {
check(wrapper.find('input[type="checkbox"]'));
wrapper.find('button').simulate('click');
});

it('only account name input is visible at first', () => {
expect(wrapper.find(InputWithLabel).find('[data-input-name]').find(Input)).toHaveLength(1);
expect(wrapper.find(InputWithLabel).find('[data-input-password]')).toHaveLength(0);
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
expect(wrapper.find(Button)).toHaveLength(0);
});

it('after typing less than 3 characters into name input, password input is not visible', () => {
type(wrapper.find('input'), 'ab');
expect(wrapper.find(Input).prop('withError')).toBe(true);
expect(wrapper.find(InputWithLabel).find('[data-input-password]')).toHaveLength(0);
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
expect(wrapper.find(Button)).toHaveLength(0);
});

it('after typing 3 characters into name input, first password input is visible', () => {
type(wrapper.find('input'), 'abc');
expect(wrapper.find(Input).first().prop('withError')).toBe(false);
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
expect(wrapper.find(Button)).toHaveLength(0);
});

it('password shorter than 6 characters should be not valid', () => {
type(wrapper.find('input'), 'abc');
type(wrapper.find('input[type="password"]'), 'abcde');
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input).prop('withError')).toBe(true);
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
expect(wrapper.find(Button)).toHaveLength(0);
});

it('submit button is not visible until both passwords are equal', () => {
type(wrapper.find('input'), 'abc');
type(wrapper.find('input[type="password"]').first(), 'abcdef');
type(wrapper.find('input[type="password"]').last(), 'abcdeg');
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(true);
expect(wrapper.find(Button)).toHaveLength(0);
});

it('submit button is visible when both passwords are equal', () => {
type(wrapper.find('input'), 'abc');
type(wrapper.find('input[type="password"]').first(), 'abcdef');
type(wrapper.find('input[type="password"]').last(), 'abcdef');
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(false);
expect(wrapper.find(Button)).toHaveLength(1);
});

it('saves account with provided name and password', async () => {
type(wrapper.find('input'), 'abc');
type(wrapper.find('input[type="password"]').first(), 'abcdef');
type(wrapper.find('input[type="password"]').last(), 'abcdef');
wrapper.find(Button).find('button').simulate('click');
expect(messaging.createAccountSuri).toBeCalledWith('abc', 'abcdef', exampleAccount.seed);
await flushAllPromises();
expect(onActionStub).toBeCalledWith('/');
});
});
});
7 changes: 2 additions & 5 deletions packages/extension-ui/src/Popup/CreateAccount/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import { ActionContext, Header, Loading } from '../../components';
import { createAccountSuri, createSeed } from '../../messaging';
import Mnemonic from '@polkadot/extension-ui/Popup/CreateAccount/Mnemonic';
import CreationStep from '@polkadot/extension-ui/Popup/CreateAccount/CreationStep';
import { RouterProps } from 'react-router';
import AccountName from '@polkadot/extension-ui/Popup/CreateAccount/AccountName';

type Props = {}

export default function CreateAccount ({ history }: RouterProps): React.ReactElement<Props> {
export default function CreateAccount (): React.ReactElement {
const onAction = useContext(ActionContext);
const [step, setStep] = useState(1);
const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
Expand All @@ -40,7 +37,7 @@ export default function CreateAccount ({ history }: RouterProps): React.ReactEle
if (step === 2) {
setStep(step - 1);
} else {
history.push('/');
onAction('/');
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/extension-ui/src/components/InputWithLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ function InputWithLabel ({ className, defaultValue, label, isFocused, isReadOnly
}

export default styled(InputWithLabel)`
padding: ${({ label, theme }): string => label ? theme.inputPaddingLabel : theme.inputPadding};
margin-bottom: 16px;
`;
2 changes: 1 addition & 1 deletion packages/extension-ui/src/components/MnemonicSeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const MnemonicText = styled(TextAreaWithLabel).attrs(() => ({
isReadOnly: true
}))`
textarea {
font-weight: 600;
font-size: ${({ theme }): string => theme.fontSize};
line-height: ${({ theme }): string => theme.lineHeight};
height: unset;
letter-spacing: -0.01em;
padding: 14px;
color: ${({ theme }): string => theme.primaryColor};
Expand Down
5 changes: 4 additions & 1 deletion packages/extension-ui/src/components/TextInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ const ErroredTextInputColors = css`
`;

const TextInput = css<Props>`
box-shadow: 0 0 40px rgba(0, 0, 0, 0.06);
border-radius: ${({ theme }): string => theme.borderRadius};
border: ${({ theme }): string => `1px solid ${theme.inputBorder}`};
box-shadow: 0 0 40px rgba(0, 0, 0, 0.06);
box-sizing: border-box;
display: block;
font-family: ${({ theme }): string => theme.fontFamily};
font-size: ${({ theme }): string => theme.fontSize};
font-weight: 600;
height: ${({ theme }): string => theme.inputHeight};
padding: ${({ theme }): string => theme.inputPadding};
resize: none;
width: 100%;
${({ withError }): typeof ErroredTextInputColors => (withError ? ErroredTextInputColors : DefaultTextInputColors)};
Expand Down
5 changes: 2 additions & 3 deletions packages/extension-ui/src/components/View.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ export default styled(View)`
font-size: ${({ theme }): string => theme.fontSize};
line-height: ${({ theme }): string => theme.lineHeight};
height: 100%;
//padding: 0 1rem;

> * {
margin-left: 1rem;
margin-right: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}

h3 {
Expand Down
2 changes: 1 addition & 1 deletion packages/extension-ui/src/components/Warning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function Warning ({ children, className, danger }: Props): React.ReactElement<Pr
export default styled(Warning)`
display: flex;
flex-direction: row;
padding-left: ${({ danger }): string => danger ? '16px' : '0'};
padding-left: ${({ danger }): string => danger ? '16px' : ''};
border-left: ${({ danger, theme }): string => danger ? `0.25rem solid ${theme.linkColorDanger}` : ''};

> div {
Expand Down
2 changes: 1 addition & 1 deletion packages/extension-ui/src/components/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const defaultTheme = {
iconDangerColor: '#FF5858',
identiconBackground: '#373737',
inputBorder: '#303030',
inputHeight: '40px',
inputPadding: '0.5rem 0.75rem',
inputPaddingLabel: '1.25rem 0.75rem 0.5rem',
labelColor: LABEL_COLOR,
labelFontSize: '13px',
labelLineHeight: '18px',
Expand Down
3 changes: 2 additions & 1 deletion packages/extension-ui/src/partials/Name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface Props {

const MIN_LENGTH = 3;

export default function Name ({ address, className, defaultValue, isFocussed, label = 'a descriptive name for this account', onBlur, onChange }: Props): React.ReactElement<Props> {
export default function Name ({ address, className, defaultValue, isFocussed, label = 'A descriptive name for your account', onBlur, onChange }: Props): React.ReactElement<Props> {
const accounts = useContext(AccountContext);
const [name, setName] = useState('');
const account = accounts.find((account): boolean => account.address === address);
Expand All @@ -37,6 +37,7 @@ export default function Name ({ address, className, defaultValue, isFocussed, la

return (
<InputWithLabel
data-input-name
className={className}
defaultValue={startValue}
isError={isError}
Expand Down
6 changes: 4 additions & 2 deletions packages/extension-ui/src/partials/Password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ export default function Password ({ isFocussed, onChange }: Props): React.ReactE
return (
<>
<InputWithLabel
data-input-password
isError={pass1.length < MIN_LENGTH}
isFocused={isFocussed}
label='a new password for this account'
label='A new password for this account'
onChange={setPass1}
type='password'
/>
{(pass1.length >= MIN_LENGTH) && (
<InputWithLabel
data-input-repeat-password
isError={pass1 !== pass2}
label='repeat password for verification'
label='Repeat password for verification'
onChange={setPass2}
type='password'
/>
Expand Down