From f9250b71494161b5483bc27da16ce456f339e87c Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Mon, 2 Jul 2018 17:25:53 +0200 Subject: [PATCH] Add tests for sendStore --- packages/fether-react/src/Send/Send.js | 4 +- .../fether-react/src/Send/Signer/Signer.js | 6 +- .../fether-react/src/Send/TxForm/TxForm.js | 11 +- packages/fether-react/src/stores/sendStore.js | 13 +- .../fether-react/src/stores/sendStore.spec.js | 210 +++++++++++++++++- .../src/utils/testHelpers/storeTests.js | 16 +- 6 files changed, 234 insertions(+), 26 deletions(-) diff --git a/packages/fether-react/src/Send/Send.js b/packages/fether-react/src/Send/Send.js index 41efc09a..f460e272 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 3793ed5d..c91324f2 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 0ce5df40..14f62838 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 125255e6..65ac2a07 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 6051b93b..28d25fd9 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 2affb215..6788a4db 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'); + }); }); -- GitLab