diff --git a/packages/fether-react/src/Send/Send.js b/packages/fether-react/src/Send/Send.js
index 41efc09a8536f5916004e45b19938f11ee31d916..f460e2724f0bd28f221bbef50c06739cb7408c07 100644
--- a/packages/fether-react/src/Send/Send.js
+++ b/packages/fether-react/src/Send/Send.js
@@ -16,12 +16,12 @@ import TxForm from './TxForm';
class Send extends Component {
render () {
const {
- sendStore: { token }
+ sendStore: { tokenAddress }
} = this.props;
// We only show then Send components if we have already selected a token to
// send.
- if (!token) {
+ if (!tokenAddress) {
return ;
}
diff --git a/packages/fether-react/src/Send/Signer/Signer.js b/packages/fether-react/src/Send/Signer/Signer.js
index 3793ed5dcc3e3ad41828d0a2ac8bb9463e90a69d..c91324f215b41abed11d39b66fd1f35b99d9da29 100644
--- a/packages/fether-react/src/Send/Signer/Signer.js
+++ b/packages/fether-react/src/Send/Signer/Signer.js
@@ -12,7 +12,7 @@ import ReactTooltip from 'react-tooltip';
import TokenBalance from '../../Tokens/TokensList/TokenBalance';
-@inject('sendStore')
+@inject('sendStore', 'tokensStore')
@observer
class Signer extends Component {
state = {
@@ -58,9 +58,11 @@ class Signer extends Component {
render () {
const {
- sendStore: { token, tx }
+ sendStore: { tokenAddress, tx },
+ tokensStore
} = this.props;
const { error, isSending, password } = this.state;
+ const token = tokensStore.tokens[tokenAddress];
return (
diff --git a/packages/fether-react/src/Send/TxForm/TxForm.js b/packages/fether-react/src/Send/TxForm/TxForm.js
index 0ce5df404d62c2b34be04a5dcfe1e3bc9ac3c341..14f6283842375f5df7469b6e86bc5f968a909cfe 100644
--- a/packages/fether-react/src/Send/TxForm/TxForm.js
+++ b/packages/fether-react/src/Send/TxForm/TxForm.js
@@ -18,8 +18,11 @@ import withBalance from '../../utils/withBalance';
const MAX_GAS_PRICE = 40; // In Gwei
const MIN_GAS_PRICE = 3; // Safelow gas price from GasStation, in Gwei
-@inject('sendStore')
-@withBalance(({ sendStore: { token } }) => token)
+@inject('sendStore', 'tokensStore')
+@withBalance(
+ ({ sendStore: { tokenAddress }, tokensStore }) =>
+ tokensStore.tokens[tokenAddress]
+)
@observer
class Send extends Component {
state = {
@@ -106,10 +109,12 @@ class Send extends Component {
render () {
const {
- sendStore: { token }
+ sendStore: { tokenAddress },
+ tokensStore
} = this.props;
const { amount, gasPrice, maxAmount, to } = this.state;
+ const token = tokensStore.tokens[tokenAddress];
const error = this.hasError();
return (
diff --git a/packages/fether-react/src/stores/sendStore.js b/packages/fether-react/src/stores/sendStore.js
index 125255e6feabf6f8f38b14529fb76efe29e7dded..65ac2a07e84eec119ef2e4b0a450dc78141d7d58 100644
--- a/packages/fether-react/src/stores/sendStore.js
+++ b/packages/fether-react/src/stores/sendStore.js
@@ -19,7 +19,7 @@ const debug = Debug('sendStore');
const DEFAULT_GAS = new BigNumber(21000); // Default gas amount to show
const GAS_MULT_FACTOR = 1.33; // Since estimateGas is not always accurate, we add a 33% factor for buffer.
-class SendStore {
+export class SendStore {
@observable blockNumber; // Current block number, used to calculate tx confirmations.
@observable estimated = DEFAULT_GAS; // Estimated gas amount for this transaction.
@observable tokenAddress; // 'ETH', or the token contract address
@@ -70,6 +70,10 @@ class SendStore {
* Estimate the amount of gas for our transaction.
*/
estimateGas = () => {
+ if (!this.tx || !Object.keys(this.tx).length) {
+ return;
+ }
+
if (this.tokenAddress === 'ETH') {
return this.estimateGasForEth(this.txForEth);
} else {
@@ -130,11 +134,6 @@ class SendStore {
});
};
- @computed
- get token () {
- return tokensStore.tokens[this.tokenAddress];
- }
-
/**
* This.tx is a user-friendly tx object. We convert it now as it can be
* passed to makeContract$(...).
@@ -145,7 +144,7 @@ class SendStore {
args: [
this.tx.to,
new BigNumber(this.tx.amount).mul(
- new BigNumber(10).pow(this.token.decimals)
+ new BigNumber(10).pow(tokensStore.tokens[this.tokenAddress].decimals)
)
],
options: {
diff --git a/packages/fether-react/src/stores/sendStore.spec.js b/packages/fether-react/src/stores/sendStore.spec.js
index 6051b93bb034eadfd737b8b4de91eaa271aedecf..28d25fd9eb4131e804f8347e374686b196f89e8f 100644
--- a/packages/fether-react/src/stores/sendStore.spec.js
+++ b/packages/fether-react/src/stores/sendStore.spec.js
@@ -5,28 +5,222 @@
/* eslint-env jest */
-import sendStore from './sendStore';
+import abi from '@parity/shared/lib/contracts/abi/eip20';
+import BigNumber from 'bignumber.js';
+import lightJs from '@parity/light.js'; // Mocked
+
+import parityStore from './parityStore';
+import { SendStore } from './sendStore';
import * as storeTests from '../utils/testHelpers/storeTests';
-storeTests.setterTest(sendStore, 'blockNumber');
-storeTests.setterTest(sendStore, 'tokenAddress');
-storeTests.setterTest(sendStore, 'tx');
-storeTests.setterTest(sendStore, 'txStatus');
+jest.mock('@parity/light.js', () => ({
+ blockNumber$: jest.fn(() => ({
+ subscribe: () =>
+ jest.fn(() => ({
+ unsubscribe: jest.fn()
+ }))
+ })),
+ makeContract$: jest.fn(() => ({
+ contractObject: {
+ instance: {
+ transfer: { estimateGas: jest.fn(() => Promise.resolve(123)) }
+ }
+ },
+ transfer$: jest.fn(() => ({ subscribe: jest.fn() }))
+ })),
+ post$: jest.fn(() => ({ subscribe: jest.fn() }))
+}));
+
+jest.mock('./parityStore', () => ({
+ api: {
+ eth: {
+ estimateGas: jest.fn(() => Promise.resolve(123))
+ },
+ signer: {
+ confirmRequest: jest.fn(() => Promise.resolve(true))
+ }
+ }
+}));
+
+jest.mock('./tokensStore', () => ({
+ tokens: {
+ ETH: { decimals: 18 },
+ foo: { decimals: 18 }
+ }
+}));
+
+const mockTx = {
+ amount: 0.01, // In Ether or in token
+ gasPrice: 4, // in Gwei
+ to: '0x123'
+};
+
+let sendStore; // Will hold the newly created instance of SendStore in each test
+beforeEach(() => {
+ sendStore = new SendStore();
+});
+
+describe('method acceptRequest', () => {
+ test('should call api.signer.confirmRequest', () => {
+ sendStore.acceptRequest(1, 'foo');
+ expect(parityStore.api.signer.confirmRequest).toHaveBeenCalledWith(
+ 1,
+ null,
+ 'foo'
+ );
+ });
+
+ test('should set a subscription on blockNumber$', () => {
+ sendStore.acceptRequest(1, 'foo');
+ expect(lightJs.blockNumber$).toHaveBeenCalled();
+ });
+});
+
+describe('method clear', () => {
+ test('should clear tx', () => {
+ sendStore.setTx(mockTx);
+ sendStore.clear();
+ expect(sendStore.tx).toEqual({});
+ });
+});
describe('@computed confirmations', () => {
- test('return correct value if txStatus is not set', () => {
+ test('should return correct value if txStatus is not set', () => {
sendStore.setTxStatus(null);
expect(sendStore.confirmations).toBe(-1);
});
- test('return correct value if txStatus is not `confirmed`', () => {
+ test('should return correct value if txStatus is not `confirmed`', () => {
sendStore.setTxStatus({ estimating: true });
expect(sendStore.confirmations).toBe(-1);
});
- test('return correct value if txStatus is `confirmed`', () => {
+ test('should return correct value if txStatus is `confirmed`', () => {
sendStore.setBlockNumber(5);
sendStore.setTxStatus({ confirmed: { blockNumber: 4 } });
expect(sendStore.confirmations).toBe(1);
});
});
+
+describe('@computed contract', () => {
+ test('should create a contract with correct token address if the current token Erc20', () => {
+ sendStore.setTokenAddress('foo');
+ sendStore.contract; // eslint-disable-line
+ expect(lightJs.makeContract$).toHaveBeenCalledWith('foo', abi);
+ });
+
+ test('should return null if the current token is ETH', () => {
+ sendStore.setTokenAddress('ETH');
+ expect(sendStore.contract).toBe(null);
+ });
+});
+
+describe('method estimateGas', () => {
+ test('should not estimate if no tx is set', () => {
+ sendStore.estimateGasForErc20 = jest.fn();
+ sendStore.estimateGasForEth = jest.fn();
+ expect(sendStore.estimateGas()).toBe(undefined);
+ expect(sendStore.estimateGasForErc20).not.toHaveBeenCalled();
+ expect(sendStore.estimateGasForEth).not.toHaveBeenCalled();
+ });
+
+ test('should call estimateGasForErc20 if the current token is Erc20', () => {
+ sendStore.estimateGasForErc20 = jest.fn(() => 'estimateGasForErc20');
+ sendStore.setTokenAddress('foo');
+ sendStore.setTx(mockTx);
+ expect(sendStore.estimateGas()).toBe('estimateGasForErc20');
+ expect(sendStore.estimateGasForErc20).toHaveBeenCalled();
+ });
+
+ test('should call estimateGasForEth if the current token is ETH', () => {
+ sendStore.estimateGasForEth = jest.fn(() => 'estimateGasForEth');
+ sendStore.setTokenAddress('ETH');
+ sendStore.setTx(mockTx);
+ expect(sendStore.estimateGas()).toBe('estimateGasForEth');
+ expect(sendStore.estimateGasForEth).toHaveBeenCalled();
+ });
+});
+
+describe('method estimateGasForErc20', () => {
+ beforeEach(() => {
+ sendStore.setTokenAddress('foo');
+ });
+
+ test('should call the transfer method on the contract', () => {
+ sendStore.estimateGasForErc20(mockTx);
+ expect(
+ sendStore.contract.contractObject.instance.transfer.estimateGas
+ ).toHaveBeenCalledWith(mockTx);
+ });
+
+ test('should memoize result', () => {
+ const a = sendStore.estimateGasForErc20(mockTx);
+ const b = sendStore.estimateGasForErc20(mockTx);
+ expect(a).toBe(b);
+ });
+});
+
+describe('method estimateGasForEth', () => {
+ beforeEach(() => {
+ sendStore.setTokenAddress('ETH');
+ });
+
+ test('should call api.eth.estimateGas', () => {
+ sendStore.estimateGasForEth(mockTx);
+ expect(parityStore.api.eth.estimateGas).toHaveBeenCalledWith(mockTx);
+ });
+
+ test('should memoize result', () => {
+ const a = sendStore.estimateGasForEth(mockTx);
+ const b = sendStore.estimateGasForEth(mockTx);
+ expect(a).toBe(b);
+ });
+});
+
+describe('method send', () => {
+ beforeEach(() => {
+ sendStore.setTx(mockTx);
+ });
+
+ test('should call transfer$ if the token is Erc20 and subscribe to it', () => {
+ sendStore.setTokenAddress('foo');
+ sendStore.send();
+ expect(sendStore.contract.transfer$).toHaveBeenCalledWith(
+ sendStore.txForErc20
+ );
+ });
+
+ test('should call post$ if the token is ETH and subscribe to it', () => {
+ sendStore.setTokenAddress('ETH');
+ sendStore.send();
+ expect(lightJs.post$).toHaveBeenCalledWith(sendStore.txForEth);
+ });
+});
+
+describe('@computed txForErc20', () => {
+ test('should return correct value', () => {
+ sendStore.setTokenAddress('foo');
+ sendStore.setTx(mockTx);
+ expect(sendStore.txForErc20).toEqual({
+ args: ['0x123', new BigNumber('10000000000000000')],
+ options: { gasPrice: new BigNumber('4000000000') }
+ });
+ });
+});
+
+describe('@computed txForEth', () => {
+ test('should return correct value', () => {
+ sendStore.setTokenAddress('foo');
+ sendStore.setTx(mockTx);
+ expect(sendStore.txForEth).toEqual({
+ gasPrice: new BigNumber('4000000000'),
+ to: '0x123',
+ value: new BigNumber('10000000000000000')
+ });
+ });
+});
+
+storeTests.setterTest(SendStore, 'blockNumber');
+storeTests.setterTest(SendStore, 'tokenAddress');
+storeTests.setterTest(SendStore, 'tx');
+storeTests.setterTest(SendStore, 'txStatus');
diff --git a/packages/fether-react/src/utils/testHelpers/storeTests.js b/packages/fether-react/src/utils/testHelpers/storeTests.js
index 2affb215ad111ae5354315cfc437c35a351480b8..6788a4dba66e05ebe27cc22162a24b159ade0a46 100644
--- a/packages/fether-react/src/utils/testHelpers/storeTests.js
+++ b/packages/fether-react/src/utils/testHelpers/storeTests.js
@@ -7,8 +7,16 @@
import capitalize from 'capitalize';
-export const setterTest = (store, variableName) =>
- test(`should correctly set ${variableName}`, () => {
- store[`set${capitalize(variableName)}`]('foo');
- expect(store[variableName]).toEqual('foo');
+let store;
+
+export const setterTest = (Store, variableName) =>
+ describe(`setter ${variableName}`, () => {
+ beforeEach(() => {
+ store = new Store();
+ });
+
+ test(`should correctly set ${variableName}`, () => {
+ store[`set${capitalize(variableName)}`]('foo');
+ expect(store[variableName]).toEqual('foo');
+ });
});