Merge pull request #3823 from ethcore/jg-typedinput-tests
Fixed bool dropdown in contract execution
This commit is contained in:
		
						commit
						3a16247655
					
				| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| 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(); | ||||
| } | ||||
|  | ||||
| @ -58,23 +58,23 @@ export default class DetailsStep extends Component { | ||||
|       <Form> | ||||
|         { this.renderWarning() } | ||||
|         <AddressSelect | ||||
|           label='from account' | ||||
|           hint='the account to transact with' | ||||
|           value={ fromAddress } | ||||
|           error={ fromAddressError } | ||||
|           accounts={ accounts } | ||||
|           balances={ balances } | ||||
|           onChange={ onFromAddressChange } /> | ||||
|           error={ fromAddressError } | ||||
|           hint='the account to transact with' | ||||
|           label='from account' | ||||
|           onChange={ onFromAddressChange } | ||||
|           value={ fromAddress } /> | ||||
|         { this.renderFunctionSelect() } | ||||
|         { this.renderParameters() } | ||||
|         <div className={ styles.columns }> | ||||
|           <div> | ||||
|             <Input | ||||
|               label='transaction value (in ETH)' | ||||
|               hint='the amount to send to with the transaction' | ||||
|               value={ amount } | ||||
|               error={ amountError } | ||||
|               onSubmit={ onAmountChange } /> | ||||
|               hint='the amount to send to with the transaction' | ||||
|               label='transaction value (in ETH)' | ||||
|               onSubmit={ onAmountChange } | ||||
|               value={ amount } /> | ||||
|           </div> | ||||
|           <div> | ||||
|             <Checkbox | ||||
|  | ||||
| @ -0,0 +1,83 @@ | ||||
| // 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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| 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( | ||||
|     <ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }> | ||||
|       <DetailsStep | ||||
|         { ...props } | ||||
|         contract={ CONTRACT } | ||||
|         onAmountChange={ onAmountChange } | ||||
|         onClose={ onClose } | ||||
|         onFromAddressChange={ onFromAddressChange } | ||||
|         onFuncChange={ onFuncChange } | ||||
|         onGasEditClick={ onGasEditClick } | ||||
|         onValueChange={ onValueChange } /> | ||||
|     </ContextProvider> | ||||
|   ); | ||||
| 
 | ||||
|   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); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -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) { | ||||
|  | ||||
							
								
								
									
										69
									
								
								js/src/modals/ExecuteContract/executeContract.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								js/src/modals/ExecuteContract/executeContract.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| 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( | ||||
|     <ExecuteContract | ||||
|       { ...props } | ||||
|       contract={ CONTRACT } | ||||
|       onClose={ onClose } | ||||
|       onFromAddressChange={ onFromAddressChange } />, | ||||
|       { 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]); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										64
									
								
								js/src/modals/ExecuteContract/executeContract.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								js/src/modals/ExecuteContract/executeContract.test.js
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| 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 | ||||
| }; | ||||
| @ -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 ( | ||||
|       <SelectField | ||||
|         className={ className } | ||||
|         autoComplete='off' | ||||
|         className={ className } | ||||
|         disabled={ disabled } | ||||
|         errorText={ error } | ||||
|         floatingLabelFixed | ||||
|         floatingLabelText={ label } | ||||
|         fullWidth | ||||
|         hintText={ hint } | ||||
|         name={ NAME_ID } | ||||
|         id={ NAME_ID } | ||||
|         underlineDisabledStyle={ UNDERLINE_DISABLED } | ||||
|         underlineStyle={ UNDERLINE_NORMAL } | ||||
|         value={ value } | ||||
|         name={ NAME_ID } | ||||
|         onBlur={ onBlur } | ||||
|         onChange={ onChange } | ||||
|         onKeyDown={ onKeyDown }> | ||||
|         onKeyDown={ onKeyDown } | ||||
|         underlineDisabledStyle={ UNDERLINE_DISABLED } | ||||
|         underlineStyle={ UNDERLINE_NORMAL } | ||||
|         value={ value }> | ||||
|         { children } | ||||
|       </SelectField> | ||||
|     ); | ||||
|  | ||||
| @ -289,9 +289,8 @@ export default class TypedInput extends Component { | ||||
|       return ( | ||||
|         <MenuItem | ||||
|           key={ bool } | ||||
|           value={ bool } | ||||
|           label={ bool } | ||||
|         > | ||||
|           value={ bool }> | ||||
|           { bool } | ||||
|         </MenuItem> | ||||
|       ); | ||||
| @ -299,19 +298,23 @@ export default class TypedInput extends Component { | ||||
| 
 | ||||
|     return ( | ||||
|       <Select | ||||
|         label={ label } | ||||
|         hint={ hint } | ||||
|         value={ value ? 'true' : 'false' } | ||||
|         error={ error } | ||||
|         hint={ hint } | ||||
|         label={ label } | ||||
|         onChange={ this.onChangeBool } | ||||
|       > | ||||
|         value={ | ||||
|           value | ||||
|             ? 'true' | ||||
|             : 'false' | ||||
|         }> | ||||
|         { boolitems } | ||||
|       </Select> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   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 = () => { | ||||
|  | ||||
							
								
								
									
										70
									
								
								js/src/ui/Form/TypedInput/typedInput.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								js/src/ui/Form/TypedInput/typedInput.spec.js
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| 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( | ||||
|     <ContextProvider api={ {} } muiTheme={ muiTheme } store={ {} }> | ||||
|       <TypedInput | ||||
|         { ...props } | ||||
|         onChange={ onChange } /> | ||||
|     </ContextProvider> | ||||
|   ); | ||||
| 
 | ||||
|   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); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -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 ( | ||||
|         <ContractIcon | ||||
|           className={ classes } | ||||
|           style={ { width: size, height: size, background: '#eee' } } /> | ||||
|           style={ { | ||||
|             width: size, | ||||
|             height: size, | ||||
|             background: '#eee' | ||||
|           } } /> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <img | ||||
|         className={ classes } | ||||
|         src={ iconsrc } | ||||
|         height={ size } | ||||
|         width={ size } | ||||
|         height={ size } /> | ||||
|         src={ iconsrc } /> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user