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 8a5ccc37..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", diff --git a/public/data/appConfig.json b/public/data/appConfig.json index c9c3ca0c..93b1520b 100644 --- a/public/data/appConfig.json +++ b/public/data/appConfig.json @@ -58,11 +58,11 @@ "fiatValueTickers": [ { - "network": "ethereum", + "network": "eth", "url": "https://api.coinmarketcap.com/v1/ticker/ethereum/" }, { - "network": "ethereum-classic", + "network": "etc", "url": "https://api.coinmarketcap.com/v1/ticker/ethereum-classic/" } ], diff --git a/src/actions/BlockchainActions.js b/src/actions/BlockchainActions.js index 353cd345..5414df9d 100644 --- a/src/actions/BlockchainActions.js +++ b/src/actions/BlockchainActions.js @@ -111,7 +111,7 @@ export const onBlockMined = (coinInfo: any): PromiseAction => async (dispa dispatch( Web3Actions.updateAccount(accounts[i], a, network) ) } else { // there are no new txs, just update block - dispatch( AccountsActions.update( { ...accounts[i], ...a }) ); + dispatch( AccountsActions.update( { ...accounts[i], block: a.block }) ); // HACK: since blockbook can't work with smart contracts for now // try to update tokens balances added to this account using Web3 diff --git a/src/actions/SendFormActions.js b/src/actions/SendFormActions.js index 6288c9d6..6b59279a 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; - } - } */ - - /* 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 + const addressError = validateAddress(state.address); + if (addressError) { + errors.address = addressError; + } + + // 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 5e65eed9..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,9 @@ const IconWrapper = styled.div` &:hover, &:focus { - border: 1px solid ${colors.GREEN_PRIMARY}; - color: ${colors.WHITE}; + ${props => !props.checked && css` + border: 1px solid ${colors.GREEN_PRIMARY}; + `} background: ${props => (props.checked ? colors.GREEN_PRIMARY : colors.WHITE)}; } `; 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/Icon/index.js b/src/components/Icon/index.js index 6e5b0d71..1879547c 100644 --- a/src/components/Icon/index.js +++ b/src/components/Icon/index.js @@ -27,7 +27,7 @@ const SvgWrapper = styled.svg` :hover { path { - fill: ${props => props.hoverColor || colors.TEXT_SECONDARY} + fill: ${props => props.hoverColor} } } `; 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/inputs/Input/index.js b/src/components/inputs/Input/index.js index 339b73cc..5bd17dc5 100644 --- a/src/components/inputs/Input/index.js +++ b/src/components/inputs/Input/index.js @@ -114,6 +114,7 @@ class Input 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 - +