diff --git a/.eslintrc b/.eslintrc index bbe3a8a7..5c87b414 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,6 +4,9 @@ "plugin:flowtype/recommended", "plugin:jest/recommended" ], + "globals": { + "COMMITHASH": true + }, "env": { "browser": true, "jest": true diff --git a/jest.config.js b/jest.config.js index d7e1c86a..d9c12451 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,11 +1,19 @@ module.exports = { rootDir: './src', + automock: false, + coverageDirectory: 'coverage/', collectCoverage: true, testURL: 'http://localhost', modulePathIgnorePatterns: [ 'node_modules', + 'utils/windowUtils.js', + 'utils/promiseUtils.js', + 'utils/networkUtils.js', ], collectCoverageFrom: [ 'utils/**.js', ], + setupFiles: [ + './support/setupJest.js', + ], }; diff --git a/package.json b/package.json index d920fb90..b3ba4764 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,11 @@ "ethereumjs-tx": "^1.3.3", "ethereumjs-units": "^0.2.0", "ethereumjs-util": "^5.1.4", + "git-revision-webpack-plugin": "^3.0.3", + "flow-webpack-plugin": "^1.2.0", "hdkey": "^0.8.0", "html-webpack-plugin": "^3.2.0", + "jest-fetch-mock": "^1.6.5", "npm-run-all": "^4.1.3", "prop-types": "^15.6.2", "raf": "^3.4.0", @@ -47,8 +50,7 @@ "react-router-redux": "next", "react-scale-text": "^1.2.2", "react-select": "2.0.0", - "react-sticky-el": "^1.0.20", - "react-transition-group": "^2.2.1", + "react-transition-group": "^2.4.0", "redbox-react": "^1.6.0", "redux": "4.0.0", "redux-logger": "^3.0.6", diff --git a/src/actions/SendFormActions.js b/src/actions/SendFormActions.js index 538ff8a5..eab6a786 100644 --- a/src/actions/SendFormActions.js +++ b/src/actions/SendFormActions.js @@ -13,6 +13,7 @@ import { initialState } from 'reducers/SendFormReducer'; import { findToken } from 'reducers/TokensReducer'; import { findDevice, getPendingAmount, getPendingNonce } from 'reducers/utils'; import * as stateUtils from 'reducers/utils'; +import { validateAddress } from 'utils/ethUtils'; import type { Dispatch, @@ -345,32 +346,19 @@ export const validation = (props: Props): void => { if (state.untouched) return; // valid address if (state.touched.address) { - /* if (state.address.length < 1) { - errors.address = 'Address is not set'; - } else if (!EthereumjsUtil.isValidAddress(state.address)) { - errors.address = 'Address is not valid'; - } else { - // address warning or info are set in addressValidation ThunkAction - // do not override this - if (state.warnings.address) { - warnings.address = state.warnings.address; - } else if (state.infos.address) { - infos.address = state.infos.address; - } - } */ + const addressError = validateAddress(state.address); + if (addressError) { + errors.address = addressError; + } - /* eslint (no-lonely-if) */ - if (state.address.length < 1) { - errors.address = 'Address is not set'; - } else if (!EthereumjsUtil.isValidAddress(state.address)) { - errors.address = 'Address is not valid'; - } else if (state.warnings.address) { - // address warning or info are set in addressValidation ThunkAction - // do not override this + // address warning or info may be set in addressValidation ThunkAction + // do not override them + if (state.warnings.address) { warnings.address = state.warnings.address; - if (state.infos.address) { - infos.address = state.infos.address; - } + } + + if (state.infos.address) { + infos.address = state.infos.address; } } diff --git a/src/components/Checkbox/index.js b/src/components/Checkbox/index.js index 1f4eef2b..fad58722 100644 --- a/src/components/Checkbox/index.js +++ b/src/components/Checkbox/index.js @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import colors from 'config/colors'; import Icon from 'components/Icon'; import icons from 'config/icons'; @@ -34,8 +34,10 @@ const IconWrapper = styled.div` &:hover, &:focus { - border: 1px solid ${colors.TEXT_PRIMARY}; - background: ${props => (props.checked ? colors.TEXT_PRIMARY : colors.WHITE)}; + ${props => !props.checked && css` + border: 1px solid ${colors.GREEN_PRIMARY}; + `} + background: ${props => (props.checked ? colors.GREEN_PRIMARY : colors.WHITE)}; } `; @@ -74,7 +76,12 @@ class Checkbox extends PureComponent { {checked && ( - + ) } diff --git a/src/components/Footer/index.js b/src/components/Footer/index.js index fa695d98..8f19462c 100644 --- a/src/components/Footer/index.js +++ b/src/components/Footer/index.js @@ -29,7 +29,7 @@ const Copy = styled.div` const Footer = ({ toggle }) => ( - © {getYear(new Date())} + © {getYear(new Date())} SatoshiLabs Terms Show Log diff --git a/src/components/Paragraph/index.js b/src/components/Paragraph/index.js index 88a24fb8..34f498de 100644 --- a/src/components/Paragraph/index.js +++ b/src/components/Paragraph/index.js @@ -26,10 +26,7 @@ const P = ({ children, className, isSmaller = false }) => ( P.propTypes = { className: PropTypes.string, isSmaller: PropTypes.bool, - children: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.string, - ]), + children: PropTypes.node, }; export default P; diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 3872a698..8f94ad15 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -3,15 +3,10 @@ import RcTooltip from 'rc-tooltip'; import colors from 'config/colors'; import styled from 'styled-components'; import PropTypes from 'prop-types'; -import { FONT_SIZE } from 'config/variables'; - -const TooltipContent = styled.div` - width: ${props => (props.isAside ? '260px' : '320px')}; - font-size: ${FONT_SIZE.SMALLEST}; -`; const Wrapper = styled.div` .rc-tooltip { + max-width: ${props => `${props.maxWidth}px` || 'auto'}; position: absolute; z-index: 1070; display: block; @@ -177,9 +172,11 @@ class Tooltip extends Component { placement, content, children, + maxWidth, } = this.props; return ( { this.tooltipContainerRef = node; }} > @@ -187,7 +184,7 @@ class Tooltip extends Component { getTooltipContainer={() => this.tooltipContainerRef} arrowContent={
} placement={placement} - overlay={{content}} + overlay={content} > {children} @@ -203,6 +200,7 @@ Tooltip.propTypes = { PropTypes.element, PropTypes.string, ]), + maxWidth: PropTypes.number, content: PropTypes.oneOfType([ PropTypes.element, PropTypes.string, diff --git a/src/components/inputs/Input/index.js b/src/components/inputs/Input/index.js index 16ec38d3..407ff1cb 100644 --- a/src/components/inputs/Input/index.js +++ b/src/components/inputs/Input/index.js @@ -113,6 +113,7 @@ class Input extends Component { )} {

{`${amount} ${currency}` }

-

{ address }

+ { address }

{ selectedFeeLevel.label }

diff --git a/src/components/modals/passphrase/Passphrase/index.js b/src/components/modals/passphrase/Passphrase/index.js index cbfc3801..f9a5e1f3 100644 --- a/src/components/modals/passphrase/Passphrase/index.js +++ b/src/components/modals/passphrase/Passphrase/index.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import raf from 'raf'; import colors from 'config/colors'; +import { H2 } from 'components/Heading'; import P from 'components/Paragraph'; import { FONT_SIZE } from 'config/variables'; import Link from 'components/Link'; @@ -23,7 +24,10 @@ const Label = styled.div` padding-bottom: 5px; `; -const PassphraseError = styled.div``; +const PassphraseError = styled.div` + margin-top: 8px; + color: ${colors.ERROR_PRIMARY}; +`; const Row = styled.div` position: relative; @@ -137,22 +141,25 @@ export default class PinModal extends Component { } = this.state; // } = this.props.modal; - let passphraseInputValue: string = passphrase; - let passphraseRevisionInputValue: string = passphraseRevision; - if (!visible && !passphraseFocused) { - passphraseInputValue = passphrase.replace(/./g, '•'); - } - if (!visible && !passphraseRevisionFocused) { - passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•'); - } + const passphraseInputValue: string = passphrase; + const passphraseRevisionInputValue: string = passphraseRevision; + // if (!visible && !passphraseFocused) { + // passphraseInputValue = passphrase.replace(/./g, '•'); + // } + // if (!visible && !passphraseRevisionFocused) { + // passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•'); + // } + if (this.passphraseInput) { - this.passphraseInput.value = passphraseInputValue; - this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password'); + // this.passphraseInput.value = passphraseInputValue; + // this.passphraseInput.setAttribute('type', visible || (!visible && !passphraseFocused) ? 'text' : 'password'); + this.passphraseInput.setAttribute('type', visible ? 'text' : 'password'); } if (this.passphraseRevisionInput) { - this.passphraseRevisionInput.value = passphraseRevisionInputValue; - this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password'); + // this.passphraseRevisionInput.value = passphraseRevisionInputValue; + // this.passphraseRevisionInput.setAttribute('type', visible || (!visible && !passphraseRevisionFocused) ? 'text' : 'password'); + this.passphraseRevisionInput.setAttribute('type', visible ? 'text' : 'password'); } } @@ -174,6 +181,14 @@ export default class PinModal extends Component { match: previousState.singleInput || previousState.passphraseRevision === value, passphrase: value, })); + + if (this.state.visible && this.passphraseRevisionInput) { + this.setState({ + match: true, + passphraseRevision: value, + }); + this.passphraseRevisionInput.value = value; + } } else { this.setState(previousState => ({ match: previousState.passphrase === value, @@ -211,12 +226,29 @@ export default class PinModal extends Component { this.setState({ visible: true, }); + if (this.passphraseRevisionInput) { + this.passphraseRevisionInput.disabled = true; + this.passphraseRevisionInput.value = this.state.passphrase; + this.setState(previousState => ({ + passphraseRevision: previousState.passphrase, + match: true, + })); + } } onPassphraseHide = (): void => { this.setState({ visible: false, }); + if (this.passphraseRevisionInput) { + const emptyPassphraseRevisionValue = ''; + this.passphraseRevisionInput.value = emptyPassphraseRevisionValue; + this.setState(previousState => ({ + passphraseRevision: emptyPassphraseRevisionValue, + match: emptyPassphraseRevisionValue === previousState.passphrase, + })); + this.passphraseRevisionInput.disabled = false; + } } submit = (empty: boolean = false): void => { @@ -232,7 +264,7 @@ export default class PinModal extends Component { //this.passphraseRevisionInput.style.display = 'none'; //this.passphraseRevisionInput.setAttribute('readonly', 'readonly'); - const p = passphrase; + // const p = passphrase; this.setState({ passphrase: '', @@ -271,11 +303,10 @@ export default class PinModal extends Component { //let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password"; const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow; - console.log('passphraseInputType', passphraseInputType); return ( - {/* ?

Enter { deviceLabel } passphrase

*/} - {/*

Note that passphrase is case-sensitive.

*/} +

Enter { deviceLabel } passphrase

+

Note that passphrase is case-sensitive.

{ data-lpignore="true" onFocus={() => this.onPassphraseFocus('passphrase')} onBlur={() => this.onPassphraseBlur('passphrase')} - tabIndex="1" + tabIndex="0" /> {!singleInput && ( @@ -306,7 +337,6 @@ export default class PinModal extends Component { data-lpignore="true" onFocus={() => this.onPassphraseFocus('revision')} onBlur={() => this.onPassphraseBlur('revision')} - tabIndex="2" /> {!match && passphraseRevisionTouched && Passphrases do not match } @@ -315,7 +345,7 @@ export default class PinModal extends Component { Show passphrase - +