diff --git a/js/src/api/util/identity.js b/js/src/api/util/identity.js index 6a25590e3db..e4a95891fb4 100644 --- a/js/src/api/util/identity.js +++ b/js/src/api/util/identity.js @@ -1,9 +1,30 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + import blockies from 'blockies'; +// jsdom doesn't have all the browser features, blockies fail +const TEST_ENV = process.env.NODE_ENV === 'test'; + export function createIdentityImg (address, scale = 8) { - return blockies({ - seed: (address || '').toLowerCase(), - size: 8, - scale - }).toDataURL(); + return TEST_ENV + ? '' + : blockies({ + seed: (address || '').toLowerCase(), + size: 8, + scale + }).toDataURL(); } diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index ae3c92e48fa..0e54e9ef485 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -58,23 +58,23 @@ export default class DetailsStep extends Component {
{ this.renderWarning() } + error={ fromAddressError } + hint='the account to transact with' + label='from account' + onChange={ onFromAddressChange } + value={ fromAddress } /> { this.renderFunctionSelect() } { this.renderParameters() }
+ hint='the amount to send to with the transaction' + label='transaction value (in ETH)' + onSubmit={ onAmountChange } + value={ amount } />
. + +import { mount } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { ContextProvider, muiTheme } from '~/ui'; + +import DetailsStep from './'; + +import { CONTRACT } from '../executeContract.test.js'; + +let component; +let onAmountChange; +let onClose; +let onFromAddressChange; +let onFuncChange; +let onGasEditClick; +let onValueChange; + +function render (props) { + onAmountChange = sinon.stub(); + onClose = sinon.stub(); + onFromAddressChange = sinon.stub(); + onFuncChange = sinon.stub(); + onGasEditClick = sinon.stub(); + onValueChange = sinon.stub(); + + component = mount( + + + + ); + + return component; +} + +describe('modals/ExecuteContract/DetailsStep', () => { + it('renders', () => { + expect(render({ accounts: {}, values: [ true ], valuesError: [ null ] })).to.be.ok; + }); + + describe('parameter values', () => { + beforeEach(() => { + render({ + accounts: {}, + func: CONTRACT.functions[0], + values: [ false ], + valuesError: [ null ] + }); + }); + + describe('bool parameters', () => { + it('toggles from false to true', () => { + component.find('DropDownMenu').last().simulate('change', { target: { value: 'true' } }); + + expect(onValueChange).to.have.been.calledWith(null, 0, true); + }); + }); + }); +}); diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index afcc826b7f2..74f36df3418 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -25,6 +25,7 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; +import { toWei } from '~/api/util/wei'; import { BusyStep, Button, CompletedStep, GasPriceEditor, IdentityIcon, Modal, TxHash } from '~/ui'; import { MAX_GAS_ESTIMATION } from '~/util/constants'; import { validateAddress, validateUint } from '~/util/validation'; @@ -56,12 +57,12 @@ class ExecuteContract extends Component { } static propTypes = { - isTest: PropTypes.bool, - fromAddress: PropTypes.string, accounts: PropTypes.object, balances: PropTypes.object, - contract: PropTypes.object, + contract: PropTypes.object.isRequired, + fromAddress: PropTypes.string, gasLimit: PropTypes.object.isRequired, + isTest: PropTypes.bool, onClose: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired } @@ -77,11 +78,11 @@ class ExecuteContract extends Component { funcError: null, gasEdit: false, rejected: false, - step: STEP_DETAILS, sending: false, + step: STEP_DETAILS, + txhash: null, values: [], - valuesError: [], - txhash: null + valuesError: [] } componentDidMount () { @@ -255,10 +256,6 @@ class ExecuteContract extends Component { valueError = validateAddress(_value).addressError; break; - case 'bool': - value = _value === 'true'; - break; - case 'uint': valueError = validateUint(_value).valueError; break; @@ -278,13 +275,12 @@ class ExecuteContract extends Component { } estimateGas = (_fromAddress) => { - const { api } = this.context; const { fromAddress } = this.props; const { amount, func, values } = this.state; const options = { gas: MAX_GAS_ESTIMATION, from: _fromAddress || fromAddress, - value: api.util.toWei(amount || 0) + value: toWei(amount || 0) }; if (!func) { diff --git a/js/src/modals/ExecuteContract/executeContract.spec.js b/js/src/modals/ExecuteContract/executeContract.spec.js new file mode 100644 index 00000000000..6a8cc692dfb --- /dev/null +++ b/js/src/modals/ExecuteContract/executeContract.spec.js @@ -0,0 +1,69 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import ExecuteContract from './'; + +import { CONTRACT, STORE } from './executeContract.test.js'; + +let component; +let onClose; +let onFromAddressChange; + +function render (props) { + onClose = sinon.stub(); + onFromAddressChange = sinon.stub(); + + component = shallow( + , + { context: { api: {}, store: STORE } } + ).find('ExecuteContract').shallow(); + + return component; +} + +describe('modals/ExecuteContract/DetailsStep', () => { + it('renders', () => { + expect(render({ accounts: {} })).to.be.ok; + }); + + describe('instance functions', () => { + beforeEach(() => { + render({ + accounts: {} + }); + }); + + describe('onValueChange', () => { + it('toggles boolean from false to true', () => { + component.setState({ + func: CONTRACT.functions[0], + values: [false] + }); + component.instance().onValueChange(null, 0, true); + + expect(component.state().values).to.deep.equal([true]); + }); + }); + }); +}); diff --git a/js/src/modals/ExecuteContract/executeContract.test.js b/js/src/modals/ExecuteContract/executeContract.test.js new file mode 100644 index 00000000000..212aba2c8b2 --- /dev/null +++ b/js/src/modals/ExecuteContract/executeContract.test.js @@ -0,0 +1,64 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import BigNumber from 'bignumber.js'; +import sinon from 'sinon'; + +const CONTRACT = { + functions: [ + { + name: 'test_a', + signature: 'test_a', + estimateGas: sinon.stub().resolves(new BigNumber(123)), + inputs: [ + { + name: 'test_bool', + kind: { + type: 'bool' + } + } + ], + abi: { + inputs: [ + { + name: 'test_bool', + type: 'bool' + } + ] + } + } + ] +}; + +const STORE = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + balances: { + balances: {} + }, + nodeStatus: { + gasLimit: new BigNumber(123) + } + }; + } +}; + +export { + CONTRACT, + STORE +}; diff --git a/js/src/ui/Form/Select/select.js b/js/src/ui/Form/Select/select.js index c6dae8b6130..f79cae58ca7 100644 --- a/js/src/ui/Form/Select/select.js +++ b/js/src/ui/Form/Select/select.js @@ -46,26 +46,26 @@ export default class Select extends Component { } render () { - const { disabled, error, label, hint, value, children, className, onBlur, onChange, onKeyDown } = this.props; + const { children, className, disabled, error, hint, label, onBlur, onChange, onKeyDown, value } = this.props; return ( + onKeyDown={ onKeyDown } + underlineDisabledStyle={ UNDERLINE_DISABLED } + underlineStyle={ UNDERLINE_NORMAL } + value={ value }> { children } ); diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js index 6383e62bc97..3d21dd6d78f 100644 --- a/js/src/ui/Form/TypedInput/typedInput.js +++ b/js/src/ui/Form/TypedInput/typedInput.js @@ -289,9 +289,8 @@ export default class TypedInput extends Component { return ( + value={ bool }> { bool } ); @@ -299,19 +298,23 @@ export default class TypedInput extends Component { return ( ); } onChangeBool = (event, _index, value) => { - this.props.onChange(value === 'true'); + // NOTE: event.target.value added for enzyme simulated event testing + this.props.onChange((value || event.target.value) === 'true'); } onEthTypeChange = () => { diff --git a/js/src/ui/Form/TypedInput/typedInput.spec.js b/js/src/ui/Form/TypedInput/typedInput.spec.js new file mode 100644 index 00000000000..e27c7482a42 --- /dev/null +++ b/js/src/ui/Form/TypedInput/typedInput.spec.js @@ -0,0 +1,70 @@ +// Copyright 2015, 2016 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { mount } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import { ContextProvider, muiTheme } from '~/ui'; +import { ABI_TYPES } from '~/util/abi'; + +import TypedInput from './'; + +let component; +let onChange; + +function render (props) { + onChange = sinon.stub(); + component = mount( + + + + ); + + return component; +} + +describe('ui/Form/TypedInput', () => { + describe('bool selection', () => { + beforeEach(() => { + render({ param: { type: ABI_TYPES.BOOL } }); + }); + + it('renders', () => { + expect(component).to.be.ok; + }); + + it('calls onChange when value changes', () => { + component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + + expect(onChange).to.have.been.called; + }); + + it("calls onChange(true) when value changes to 'true'", () => { + component.find('DropDownMenu').simulate('change', { target: { value: 'true' } }); + + expect(onChange).to.have.been.calledWith(true); + }); + + it("calls onChange(false) when value changes to 'false'", () => { + component.find('DropDownMenu').simulate('change', { target: { value: 'false' } }); + + expect(onChange).to.have.been.calledWith(false); + }); + }); +}); diff --git a/js/src/ui/IdentityIcon/identityIcon.js b/js/src/ui/IdentityIcon/identityIcon.js index 466ffa9b564..55fc34afa10 100644 --- a/js/src/ui/IdentityIcon/identityIcon.js +++ b/js/src/ui/IdentityIcon/identityIcon.js @@ -19,6 +19,8 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ContractIcon from 'material-ui/svg-icons/action/code'; +import { createIdentityImg } from '~/api/util/identity'; + import styles from './identityIcon.css'; class IdentityIcon extends Component { @@ -29,12 +31,12 @@ class IdentityIcon extends Component { static propTypes = { address: PropTypes.string, button: PropTypes.bool, - className: PropTypes.string, center: PropTypes.bool, - padded: PropTypes.bool, + className: PropTypes.string, inline: PropTypes.bool, - tiny: PropTypes.bool, - images: PropTypes.object.isRequired + images: PropTypes.object.isRequired, + padded: PropTypes.bool, + tiny: PropTypes.bool } state = { @@ -75,7 +77,7 @@ class IdentityIcon extends Component { } this.setState({ - iconsrc: api.util.createIdentityImg(_address, scale) + iconsrc: createIdentityImg(_address, scale) }); } @@ -105,16 +107,20 @@ class IdentityIcon extends Component { return ( + style={ { + width: size, + height: size, + background: '#eee' + } } /> ); } return ( + src={ iconsrc } /> ); } }