* WIP: Fixes #260. Autofocus password input field. Press tab after enter password to switch focus to submit * feat: Clicks Submit Transaction when user presses Enter after entering password * fix: Remove console logs and no need to focus * test: Add test setup. setState not changing currentItem from undefined * fix: Change tabIndex values to number instead of string * fix: Use Refs instead of JS DOM access * fix: Add missing newline * test: Test modal show after setState. Skipped pending resolution of Enzyme issue * fix: Remove string from tabIndex prop type * fix: Remove use of SUIEvent. Use React refs not HTML DOM queries * test: Update tests but skipping since awaiting resolution of enzyme issue Reference: https://github.com/airbnb/enzyme/issues/1794 * WIP * fix: Simply call onSend instead of long-winded approach with Refs * test: Fix tests to use promises instead of callbacks * fix: Fix tslint error to handle promises correctly * fix: Remove return since broke functionality. Use await to handle promise. Use key instead of keyCode
This commit is contained in:
parent
165fb68e74
commit
189e486ed8
@ -6,5 +6,8 @@ module.exports = Object.assign({}, config, {
|
||||
'@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
|
||||
'\\.(css|less)$': 'empty/object'
|
||||
}
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!polkadot-identicon)'
|
||||
]
|
||||
});
|
||||
|
||||
@ -17,7 +17,7 @@ import Or from './Or';
|
||||
|
||||
class Button extends React.PureComponent<ButtonProps> {
|
||||
render () {
|
||||
const { children, className, floated, icon, isBasic = false, isCircular = false, isDisabled = false, isNegative = false, isPositive = false, isPrimary = false, onClick, size, style, text } = this.props;
|
||||
const { children, className, floated, icon, isBasic = false, isCircular = false, isDisabled = false, isNegative = false, isPositive = false, isPrimary = false, onClick, size, style, tabIndex, text } = this.props;
|
||||
|
||||
const props = {
|
||||
basic: isBasic || false,
|
||||
@ -32,7 +32,8 @@ class Button extends React.PureComponent<ButtonProps> {
|
||||
primary: isPrimary,
|
||||
size,
|
||||
secondary: isBasic && !(isPositive || isPrimary || isNegative || false),
|
||||
style
|
||||
style,
|
||||
tabIndex
|
||||
};
|
||||
|
||||
return isUndefined(text) && isUndefined(children)
|
||||
|
||||
1
packages/ui-app/src/Button/types.d.ts
vendored
1
packages/ui-app/src/Button/types.d.ts
vendored
@ -18,6 +18,7 @@ export type ButtonProps = BareProps & {
|
||||
isPrimary?: boolean,
|
||||
onClick?: () => void | Promise<void>,
|
||||
size?: Button$Sizes,
|
||||
tabIndex?: number,
|
||||
text?: any
|
||||
};
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import Labelled from './Labelled';
|
||||
type Input$Type = 'number' | 'password' | 'text';
|
||||
|
||||
type Props = BareProps & {
|
||||
autoFocus?: boolean,
|
||||
children?: React.ReactNode,
|
||||
defaultValue?: any,
|
||||
icon?: any, // node?
|
||||
@ -27,16 +28,14 @@ type Props = BareProps & {
|
||||
min?: any,
|
||||
name?: string,
|
||||
onChange: (value: string) => void,
|
||||
onKeyDown?: (event: React.KeyboardEvent<Element>) => void,
|
||||
placeholder?: string,
|
||||
tabIndex?: number,
|
||||
type?: Input$Type,
|
||||
value?: any,
|
||||
withLabel?: boolean
|
||||
};
|
||||
|
||||
type SUIEvent = {
|
||||
value: any
|
||||
};
|
||||
|
||||
type State = {
|
||||
name: string;
|
||||
};
|
||||
@ -49,7 +48,7 @@ export default class Input extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { children, className, defaultValue, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, label, max, min, name, placeholder, style, type = 'text', value, withLabel } = this.props;
|
||||
const { autoFocus = false, children, className, defaultValue, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, label, max, min, name, placeholder, style, tabIndex, type = 'text', value, withLabel } = this.props;
|
||||
|
||||
return (
|
||||
<Labelled
|
||||
@ -59,6 +58,7 @@ export default class Input extends React.PureComponent<Props, State> {
|
||||
withLabel={withLabel}
|
||||
>
|
||||
<SUIInput
|
||||
autoFocus={autoFocus}
|
||||
action={isAction}
|
||||
className={isEditable ? 'edit icon' : ''}
|
||||
defaultValue={defaultValue}
|
||||
@ -75,7 +75,9 @@ export default class Input extends React.PureComponent<Props, State> {
|
||||
min={min}
|
||||
name={name || this.state.name}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
placeholder={placeholder}
|
||||
tabIndex={tabIndex}
|
||||
type={type}
|
||||
value={value}
|
||||
>
|
||||
@ -94,7 +96,17 @@ export default class Input extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (event: React.SyntheticEvent<Element>, { value }: SUIEvent): void => {
|
||||
this.props.onChange(value);
|
||||
onChange = (event: React.SyntheticEvent<Element>): void => {
|
||||
const { onChange } = this.props;
|
||||
|
||||
onChange((event.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
onKeyDown = (event: React.KeyboardEvent<Element>): void => {
|
||||
const { onKeyDown } = this.props;
|
||||
|
||||
if (onKeyDown) {
|
||||
onKeyDown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import Button from './Button';
|
||||
import Input from './Input';
|
||||
|
||||
type Props = BareProps & {
|
||||
autoFocus?: boolean,
|
||||
defaultValue?: any,
|
||||
children?: React.ReactNode,
|
||||
isDisabled?: boolean,
|
||||
@ -17,6 +18,8 @@ type Props = BareProps & {
|
||||
label?: string,
|
||||
name?: string,
|
||||
onChange: (value: string) => void,
|
||||
onKeyDown?: (event: React.KeyboardEvent<Element>) => void,
|
||||
tabIndex?: number,
|
||||
value: any,
|
||||
withLabel?: boolean
|
||||
};
|
||||
@ -31,11 +34,12 @@ export default class Password extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { children, className, defaultValue, isDisabled, isError, label, name, onChange, style, value, withLabel } = this.props;
|
||||
const { autoFocus, children, className, defaultValue, isDisabled, isError, label, name, onChange, onKeyDown, style, tabIndex, value, withLabel } = this.props;
|
||||
const { isVisible } = this.state;
|
||||
|
||||
return (
|
||||
<Input
|
||||
autoFocus={autoFocus}
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isAction
|
||||
@ -44,7 +48,9 @@ export default class Password extends React.PureComponent<Props, State> {
|
||||
label={label}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
type={
|
||||
isVisible
|
||||
? 'text'
|
||||
|
||||
81
packages/ui-signer/src/Modal.spec.js
Normal file
81
packages/ui-signer/src/Modal.spec.js
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-app authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from '../../../test/enzyme';
|
||||
import { Signer } from './Modal';
|
||||
|
||||
const mockT = (key, options) => (key);
|
||||
|
||||
describe('Signer', () => {
|
||||
let expectedNextCurrentItemState, fixtureCurrentItemState, fixtureQueueProp, unlockComponent, wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
fixtureCurrentItemState = {
|
||||
rpc: {
|
||||
isSigned: true
|
||||
},
|
||||
id: 'test1',
|
||||
publicKey: new Uint8Array([1]),
|
||||
status: 'sending'
|
||||
};
|
||||
|
||||
fixtureQueueProp = [{
|
||||
rpc: {
|
||||
isSigned: true
|
||||
},
|
||||
id: 'test2',
|
||||
publicKey: new Uint8Array([2]),
|
||||
status: 'queued'
|
||||
}];
|
||||
|
||||
expectedNextCurrentItemState = fixtureQueueProp[0];
|
||||
|
||||
wrapper = shallow(
|
||||
<Signer
|
||||
t={mockT}
|
||||
queue={[]}
|
||||
/>, {}
|
||||
);
|
||||
});
|
||||
|
||||
it('creates the element', () => {
|
||||
return new Promise((resolve) => {
|
||||
wrapper.setState({
|
||||
currentItem: fixtureCurrentItemState,
|
||||
password: '123',
|
||||
unlockError: null
|
||||
}, () => {
|
||||
wrapper.setProps({
|
||||
queue: fixtureQueueProp
|
||||
}, resolve);
|
||||
});
|
||||
}).then(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Reference: https://github.com/airbnb/enzyme/issues/1794
|
||||
it('renders Unlock component with empty password', () => {
|
||||
return new Promise((resolve) => {
|
||||
wrapper.setState({
|
||||
currentItem: fixtureCurrentItemState,
|
||||
password: '123',
|
||||
unlockError: null
|
||||
}, () => {
|
||||
wrapper.setProps({
|
||||
queue: fixtureQueueProp
|
||||
}, resolve);
|
||||
});
|
||||
}).then(() => {
|
||||
wrapper.update();
|
||||
expect(wrapper.state('currentItem')).toEqual(expectedNextCurrentItemState);
|
||||
expect(wrapper.find('.ui--signer-Signer')).toHaveLength(1);
|
||||
unlockComponent = wrapper.find('.ui--signer-Signer').children().at(0).childAt(0);
|
||||
expect(unlockComponent.props().password).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -106,24 +106,29 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
<Button
|
||||
isNegative
|
||||
onClick={this.onCancel}
|
||||
tabIndex={3}
|
||||
text={t('extrinsic.cancel', {
|
||||
defaultValue: 'Cancel'
|
||||
})}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isPrimary
|
||||
onClick={this.onSend}
|
||||
text={
|
||||
isSigned
|
||||
? t('extrinsic.signedSend', {
|
||||
defaultValue: 'Sign and Submit'
|
||||
})
|
||||
: t('extrinsic.send', {
|
||||
defaultValue: 'Submit'
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Button
|
||||
className='ui--signer-Signer-Submit'
|
||||
isPrimary
|
||||
onClick={this.onSend}
|
||||
tabIndex={2}
|
||||
text={
|
||||
isSigned
|
||||
? t('extrinsic.signedSend', {
|
||||
defaultValue: 'Sign and Submit'
|
||||
})
|
||||
: t('extrinsic.send', {
|
||||
defaultValue: 'Submit'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
);
|
||||
@ -153,10 +158,13 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<Unlock
|
||||
autoFocus
|
||||
error={unlockError && t(unlockError.key, unlockError.value)}
|
||||
onChange={this.onChangePassword}
|
||||
onKeyDown={this.onKeyDown}
|
||||
password={password}
|
||||
value={currentItem.publicKey}
|
||||
tabIndex={1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -189,6 +197,12 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown = async (event: React.KeyboardEvent<Element>): Promise<any> => {
|
||||
if (event.key === 'Enter') {
|
||||
await this.onSend();
|
||||
}
|
||||
}
|
||||
|
||||
onCancel = (): void => {
|
||||
const { queueSetStatus } = this.props;
|
||||
const { currentItem } = this.state;
|
||||
@ -246,4 +260,8 @@ const Component: React.ComponentType<any> = translate(
|
||||
withApi(Signer)
|
||||
);
|
||||
|
||||
export {
|
||||
Signer
|
||||
};
|
||||
|
||||
export default Component;
|
||||
|
||||
@ -13,9 +13,12 @@ import keyring from '@polkadot/ui-keyring/index';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
autoFocus?: boolean,
|
||||
error?: string,
|
||||
onChange: (password: string) => void,
|
||||
onKeyDown?: (event: React.KeyboardEvent<Element>) => void,
|
||||
password: string,
|
||||
tabIndex?: number,
|
||||
value?: Uint8Array | null
|
||||
};
|
||||
|
||||
@ -40,7 +43,7 @@ class Unlock extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { onChange, password, t } = this.props;
|
||||
const { autoFocus, onChange, onKeyDown, password, t, tabIndex } = this.props;
|
||||
const { isError, isLocked } = this.state;
|
||||
|
||||
if (!isLocked) {
|
||||
@ -51,12 +54,15 @@ class Unlock extends React.PureComponent<Props, State> {
|
||||
<div className='ui--signer-Signer-Unlock'>
|
||||
<div className='ui--row'>
|
||||
<Password
|
||||
autoFocus={autoFocus}
|
||||
className='medium'
|
||||
isError={isError}
|
||||
label={t('unlock.password', {
|
||||
defaultValue: 'unlock account using'
|
||||
})}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
value={password}
|
||||
/>
|
||||
</div>
|
||||
|
||||
12
test/enzyme.js
Normal file
12
test/enzyme.js
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2018 @polkadot authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
const Adapter = require('enzyme-adapter-react-16');
|
||||
const Enzyme = require('enzyme');
|
||||
|
||||
Enzyme.configure({
|
||||
adapter: new Adapter()
|
||||
});
|
||||
|
||||
module.exports = Enzyme;
|
||||
Loading…
Reference in New Issue
Block a user