From 189e486ed85786b4ccafb6336c0fa86997491a80 Mon Sep 17 00:00:00 2001 From: Luke Schoen Date: Thu, 13 Sep 2018 11:36:52 +0200 Subject: [PATCH] feat: Fixes Issue #260. Allow use Enter to submit extrinsics (#265) * 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 --- jest.config.js | 5 +- packages/ui-app/src/Button/index.tsx | 5 +- packages/ui-app/src/Button/types.d.ts | 1 + packages/ui-app/src/Input.tsx | 26 ++++++--- packages/ui-app/src/Password.tsx | 8 ++- packages/ui-signer/src/Modal.spec.js | 81 +++++++++++++++++++++++++++ packages/ui-signer/src/Modal.tsx | 44 ++++++++++----- packages/ui-signer/src/Unlock.tsx | 8 ++- test/enzyme.js | 12 ++++ 9 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 packages/ui-signer/src/Modal.spec.js create mode 100644 test/enzyme.js diff --git a/jest.config.js b/jest.config.js index 077344691d..0cafb2c034 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,5 +6,8 @@ module.exports = Object.assign({}, config, { '@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '/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)' + ] }); diff --git a/packages/ui-app/src/Button/index.tsx b/packages/ui-app/src/Button/index.tsx index f0a5a83a37..4c9c0f254d 100644 --- a/packages/ui-app/src/Button/index.tsx +++ b/packages/ui-app/src/Button/index.tsx @@ -17,7 +17,7 @@ import Or from './Or'; class Button extends React.PureComponent { 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 { primary: isPrimary, size, secondary: isBasic && !(isPositive || isPrimary || isNegative || false), - style + style, + tabIndex }; return isUndefined(text) && isUndefined(children) diff --git a/packages/ui-app/src/Button/types.d.ts b/packages/ui-app/src/Button/types.d.ts index b2a9ab54da..3f6d32676d 100644 --- a/packages/ui-app/src/Button/types.d.ts +++ b/packages/ui-app/src/Button/types.d.ts @@ -18,6 +18,7 @@ export type ButtonProps = BareProps & { isPrimary?: boolean, onClick?: () => void | Promise, size?: Button$Sizes, + tabIndex?: number, text?: any }; diff --git a/packages/ui-app/src/Input.tsx b/packages/ui-app/src/Input.tsx index ad898c8e9b..c4333a14f1 100644 --- a/packages/ui-app/src/Input.tsx +++ b/packages/ui-app/src/Input.tsx @@ -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) => 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 { }; 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 ( { withLabel={withLabel} > { 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 { ); } - onChange = (event: React.SyntheticEvent, { value }: SUIEvent): void => { - this.props.onChange(value); + onChange = (event: React.SyntheticEvent): void => { + const { onChange } = this.props; + + onChange((event.target as HTMLInputElement).value); + } + + onKeyDown = (event: React.KeyboardEvent): void => { + const { onKeyDown } = this.props; + + if (onKeyDown) { + onKeyDown(event); + } } } diff --git a/packages/ui-app/src/Password.tsx b/packages/ui-app/src/Password.tsx index 1b0b23f1f1..fcadd63e61 100644 --- a/packages/ui-app/src/Password.tsx +++ b/packages/ui-app/src/Password.tsx @@ -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) => void, + tabIndex?: number, value: any, withLabel?: boolean }; @@ -31,11 +34,12 @@ export default class Password extends React.PureComponent { }; 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 ( { label={label} name={name} onChange={onChange} + onKeyDown={onKeyDown} style={style} + tabIndex={tabIndex} type={ isVisible ? 'text' diff --git a/packages/ui-signer/src/Modal.spec.js b/packages/ui-signer/src/Modal.spec.js new file mode 100644 index 0000000000..1aaf3003d3 --- /dev/null +++ b/packages/ui-signer/src/Modal.spec.js @@ -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( + , {} + ); + }); + + 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(''); + }); + }); +}); diff --git a/packages/ui-signer/src/Modal.tsx b/packages/ui-signer/src/Modal.tsx index 006c2812bc..14f2aa9a5a 100644 --- a/packages/ui-signer/src/Modal.tsx +++ b/packages/ui-signer/src/Modal.tsx @@ -106,24 +106,29 @@ class Signer extends React.PureComponent {