Skip to content

Commit

Permalink
Test for Find Contract Components
Browse files Browse the repository at this point in the history
Includes refactoring of some components for clarity.
  • Loading branch information
perfectmak committed Apr 25, 2018
1 parent 5f60a28 commit de46d40
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 60 deletions.
64 changes: 17 additions & 47 deletions src/components/FindContract/FindContractField.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,37 @@
import { Form, Icon, Input, Tooltip } from 'antd';
import { Form, Input } from 'antd';
import React from 'react';

import store from '../../store';

const FormItem = Form.Item;

const ethAddressValidator = (rule, value, callback) => {
const web3 = store.getState().web3.web3Instance;

if (!web3) {
callback();
} else {
callback(web3.isAddress(value) ? undefined : 'Invalid ETH address');
}
};

const Hint = (props) => (<Tooltip title={props.hint} ><Icon type="question-circle-o" /></Tooltip>);

const fieldSettingsByName = {
marketContractAddress: {
label: 'MARKET Contract Address',
initialValue: '0x12345678123456781234567812345678',
rules: [
{
required: true, message: 'Please enter MARKET contract address',
},
{
validator: ethAddressValidator
}
],
extra: 'Please enter deployed and whitelisted MARKET contract address',

component: () => (<Input />)
},
};
const getFieldSettings = (validators) => ({
rules: [
{
required: true, message: 'Please enter MARKET contract address',
},
{
validator: validators.ethAddressValidator
}
]
});

function FindContractField(props) {
const { name, form, initialValue, showHint } = props;
const { name, form, validators } = props;
const { getFieldDecorator } = form;

const fieldSettings = fieldSettingsByName[name];
const fieldSettings = getFieldSettings(validators);

const rules = typeof fieldSettings.rules === 'function' ? fieldSettings.rules(form) : fieldSettings.rules;
const label = (<span>{fieldSettings.label} {showHint && <Hint hint={fieldSettings.extra}/>}</span>);
const rules = fieldSettings.rules;
const label = 'MARKET Contract Address';

return (
<FormItem
label={label}
label={(<span>{label}</span>)}
>

{getFieldDecorator(name, {
initialValue,
rules,
})(
fieldSettings.component({
form,
fieldSettings,
showHint
})
)}
})(<Input />)}
</FormItem>
);
}

export const FieldSettings = fieldSettingsByName;
export default FindContractField;
10 changes: 3 additions & 7 deletions src/components/FindContract/FindContractForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Button, Card, Col, Form, List, Row } from 'antd';
import React, { Component } from 'react';

import Loader from '../Loader';
import showMessage from '../message';
import Field from './FindContractField';
import FindContractSuccess from './FindContractSuccess';

Expand All @@ -11,9 +10,6 @@ class FindContractForm extends Component {
constructor(props) {
super(props);

this.showErrorMessage=showMessage.bind(showMessage, 'error');
this.showSuccessMessage=showMessage.bind(showMessage, 'success');

this.state = {
marketContractAddress: '',
contract: [],
Expand All @@ -39,9 +35,9 @@ class FindContractForm extends Component {
componentWillReceiveProps(nextProps) {
if(this.props.loading && !nextProps.loading) {
if(nextProps.error) {
this.showErrorMessage("Contract not found at this address!", 8);
this.props.showErrorMessage("Contract not found at this address!", 8);
} else if (nextProps.contract) {
this.showSuccessMessage(FindContractSuccess({ contract: nextProps.contract }), 3);
this.props.showSuccessMessage(FindContractSuccess({ contract: nextProps.contract }), 3);
}
}
}
Expand Down Expand Up @@ -77,7 +73,7 @@ class FindContractForm extends Component {
<Form onSubmit={this.handleFind.bind(this)} layout="vertical">
<ContractFormRow>
<ContractFormCol>
<Field name='marketContractAddress' form={this.props.form} showHint/>
<Field name='marketContractAddress' validators={this.props.validators} form={this.props.form}/>
</ContractFormCol>
</ContractFormRow>

Expand Down
11 changes: 10 additions & 1 deletion src/containers/Find.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { connect } from 'react-redux';

import Contracts from '../Contracts.js';
import showMessage from '../components/message';
import { findContract } from '../actions/find';
import CreateInitializer, { contractConstructor } from '../util/web3/contractInitializer';
import { processContractsList } from '../util/utils';
import FormValidators from '../util/forms/Validators';
import FindContractForm from '../components/FindContract/FindContractForm';
import store from '../store';

Expand Down Expand Up @@ -43,6 +45,13 @@ const mapDispatchToProps = dispatch => {
};
};

const Find = connect(mapStateToProps, mapDispatchToProps)(FindContractForm);
const mergeProps = (stateProps, dispatchProps, ownProps) =>
Object.assign({
validators: FormValidators(store.getState().web3.web3Instance),
showErrorMessage: showMessage.bind(showMessage, 'error'),
showSuccessMessage: showMessage.bind(showMessage, 'success'),
}, ownProps, stateProps, dispatchProps);

const Find = connect(mapStateToProps, mapDispatchToProps, mergeProps)(FindContractForm);

export default Find;
11 changes: 11 additions & 0 deletions src/util/forms/Validators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function createFormValidators(web3) {
return {
ethAddressValidator(rule, value, callback) {
if (!web3) {
callback();
} else {
callback(web3.isAddress(value) ? undefined : 'Invalid ETH address');
}
}
};
}
26 changes: 26 additions & 0 deletions test/components/FindContract/FindContractField.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { mount } from 'enzyme';

import FindContractField from '../../../src/components/FindContract/FindContractField';

describe('FindContractField', () => {
let name, form, validators;
beforeEach(() => {
name = 'marketContractAddress';
form = {
getFieldDecorator(name, params) {
return component => component;
}
};
validators = {
ethAddressValidator(rule, value, callback) {}
};
});

it('should render FindContractField successfully', () => {
const props = {
name, form, validators
};
mount(<FindContractField {...props}/>);
});
});
128 changes: 123 additions & 5 deletions test/components/FindContract/FindContractForm.test.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,147 @@
import React from 'react';
import { Form } from 'antd';
import { List } from 'antd';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import sinon from 'sinon';

import FindContractForm from '../../../src/components/FindContract/FindContractForm';

function validAddressFields() {
return {
marketContractAddress: { value: '0xf204a4ef082f5c04bb89f7d5e6568b796096735a' }
};
}

describe('FindContractForm', () => {
let findContractForm;
let wrappedFormRef;
let onFindContractSpy;
let successMessageSpy;
let errorMessageSpy;

beforeEach(() => {
onFindContractSpy = sinon.spy();
errorMessageSpy = sinon.spy();
successMessageSpy = sinon.spy();

const props = {
location: {},
onFindContract: onFindContractSpy
contract: [],
showErrorMessage: errorMessageSpy,
showSuccessMessage: successMessageSpy,
onFindContract: onFindContractSpy,
validators: { ethAddressValidator: (rule, value, callback) => callback() },
wrappedComponentRef: (inst) => wrappedFormRef = inst
};
findContractForm = shallow(<FindContractForm {...props}/>);
findContractForm = mount(<FindContractForm {...props}/>);
});

it('should findContract when props.onFindContract is invoked', () => {
findContractForm.setProps({
marketContractAddress: {
marketContractAddress: "0x12345678123456781234567812345678"
marketContractAddress: '0x12345678123456781234567812345678'
}
});
findContractForm.props().onFindContract({});
expect(onFindContractSpy).to.have.property('callCount', 1);
});

it('should not show List if contract is empty', () => {
findContractForm.setProps({
contract: []
});
expect(findContractForm.find(List)).to.have.length(0);
});

it('should show List if contract is not empty', () => {
findContractForm.setProps({
contract: [{
name: 'key',
value: '0x12345678123456781234567812345678'
}]
});
expect(findContractForm.find(List)).to.have.length(1);
});

it('should enable submit button if component is not loading and no errors', () => {
// no errors are set by default on the form
findContractForm.setProps({
loading: false
});

const submitButton = findContractForm.find('.submit-button').first();
expect(submitButton.prop('disabled')).to.equal(false);
});

it('should disable submit button when loading', () => {
findContractForm.setProps({
loading: true
});

const submitButton = findContractForm.find('.submit-button').first();

expect(submitButton.prop('disabled')).to.equal(true);
});

it('should disable submit if fields have errors', () => {
wrappedFormRef.props.form.setFields({ marketContractAddress: {
value: '',
errors: [ new Error('Market Address is required.') ]
} });

findContractForm.setProps({
loading: false
});

const submitButton = findContractForm.find('.submit-button').first();

expect(submitButton.prop('disabled')).to.equal(true);
});

it('should reset form when .reset-button is clicked', () => {
const defaultFieldValues = wrappedFormRef.props.form.getFieldsValue();
wrappedFormRef.props.form.setFields(validAddressFields());
findContractForm.setProps({
loading: false
});

const resetButton = findContractForm.find('.reset-button').first();
resetButton.simulate('click', { preventDefault() {} });

const valuesAfterReset = wrappedFormRef.props.form.getFieldsValue();
expect(valuesAfterReset).to.deep.equals(defaultFieldValues);
});

it('should call onFindContract() with form values when submitted', () => {
wrappedFormRef.props.form.setFields(validAddressFields());

findContractForm.find(Form).first().simulate('submit', { preventDefault() {} });
expect(onFindContractSpy).to.have.property('callCount', 1);
});

it('should not call onFindContract() when form is invalid.', () => {
wrappedFormRef.props.form.setFields({ marketContractAddress: {
value: '',
errors: [ new Error('Market Address is required.') ]
} });

findContractForm.find(Form).first().simulate('submit', { preventDefault() {} });
expect(onFindContractSpy).to.have.property('callCount', 0);
});

it('should showSuccessMessage when contract is found', () => {
findContractForm.setProps({ error: null, loading: true, contract: [] });
expect(successMessageSpy).to.have.property('callCount', 0);
findContractForm.setProps({ error: null, loading: false, contract: {
key: '0x00000'
} });
expect(successMessageSpy).to.have.property('callCount', 1);
});

it('should showErrorMessage is error is set', () => {
findContractForm.setProps({ error: null, loading: true});
expect(errorMessageSpy).to.have.property('callCount', 0);
findContractForm.setProps({ error: 'Error occured', loading: false });
expect(errorMessageSpy).to.have.property('callCount', 1);
});
});
13 changes: 13 additions & 0 deletions test/components/FindContract/FindContractSuccess.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';

import FindContractSuccess from '../../../src/components/FindContract/FindContractSuccess';

describe('FindContractForm', () => {

it('should render without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<FindContractSuccess contract={{key: 'Key'}}/>, div);
});

});
38 changes: 38 additions & 0 deletions test/util/forms/Validators.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect } from 'chai';
import sinon from 'sinon';

import FormValidators from '../../../src/util/forms/Validators';

describe('FormValidators', () => {

describe('ethAddressValidator', () => {
it('should invoke callback() with no argument if web3 is not provided', () => {
const callbackSpy = sinon.spy();

FormValidators().ethAddressValidator({}, '', callbackSpy);

expect(callbackSpy).to.have.property('callCount', 1);
expect(callbackSpy.getCall(0).args[0]).to.equal(undefined); // valid
});

it('should invoke callback() with message if address is invalid', () => {
const callbackSpy = sinon.spy();
const mockWeb3 = { isAddress: (any) => false };

FormValidators(mockWeb3).ethAddressValidator({}, '0xdf', callbackSpy);

expect(callbackSpy).to.have.property('callCount', 1);
expect(callbackSpy.getCall(0).args[0]).to.equal('Invalid ETH address'); // invalid
});

it('should invoke callback() with no argument if address is valid', () => {
const callbackSpy = sinon.spy();
const mockWeb3 = { isAddress: (any) => true };

FormValidators(mockWeb3).ethAddressValidator({}, '0x00003', callbackSpy);

expect(callbackSpy).to.have.property('callCount', 1);
expect(callbackSpy.getCall(0).args[0]).to.equal(undefined); // valid
});
});
});

0 comments on commit de46d40

Please sign in to comment.