From cf9887f21f5d0658bea809bce888d59fb5d48be4 Mon Sep 17 00:00:00 2001 From: aitrean Date: Thu, 8 Mar 2018 13:03:45 -0500 Subject: [PATCH] Outstanding tasks to Productionize Tx (#1194) * Verify and complete all branching saga logic tests for transaction stack. * Write reducer tests for refactored transaction stack. * Add selector tests. Some files still need to be debugged. * Add snapshot test for fields, additional seelector testing. * Remove fields snapshots. * Remove ABIs from the TestState json * Use redux state instead of raw json in selector testing. * Fix merge issues. * Remove log * Fix state values. * Change test value to wei. * Last touchup. * Fix buffer shape, change Wei typo, use reasonable wei values. * Last touch up. --- common/selectors/transaction/transaction.ts | 7 +- package.json | 24 ++-- .../transaction/broadcast/broadcast.spec.ts | 56 ++++++++ .../transaction/fields/fields.spec.ts | 122 ++++++++++++++++++ spec/reducers/transaction/meta/meta.spec.ts | 109 ++++++++++++++++ .../transaction/network/network.spec.ts | 55 ++++++++ .../reducers/transaction/signing/sign.spec.ts | 62 +++++++++ .../transaction/current/currentTo.spec.ts | 101 +++++++++++---- .../transaction/current/currentValue.spec.ts | 1 - spec/sagas/transaction/fields/fields.spec.ts | 22 +++- spec/sagas/transaction/sign/signing.spec.ts | 79 ++++++++++++ spec/selectors/helpers.ts | 11 ++ spec/selectors/transaction/broadcast.spec.ts | 63 +++++++++ spec/selectors/transaction/current.spec.ts | 68 ++++++++++ spec/selectors/transaction/fields.spec.ts | 88 +++++++++++++ spec/selectors/transaction/helpers.spec.ts | 99 ++++++++++++++ spec/selectors/transaction/meta.spec.ts | 70 ++++++++++ spec/selectors/transaction/network.spec.ts | 48 +++++++ spec/selectors/transaction/sign.spec.ts | 44 +++++++ 19 files changed, 1076 insertions(+), 53 deletions(-) create mode 100644 spec/reducers/transaction/broadcast/broadcast.spec.ts create mode 100644 spec/reducers/transaction/fields/fields.spec.ts create mode 100644 spec/reducers/transaction/meta/meta.spec.ts create mode 100644 spec/reducers/transaction/network/network.spec.ts create mode 100644 spec/reducers/transaction/signing/sign.spec.ts create mode 100644 spec/sagas/transaction/sign/signing.spec.ts create mode 100644 spec/selectors/helpers.ts create mode 100644 spec/selectors/transaction/broadcast.spec.ts create mode 100644 spec/selectors/transaction/current.spec.ts create mode 100644 spec/selectors/transaction/fields.spec.ts create mode 100644 spec/selectors/transaction/helpers.spec.ts create mode 100644 spec/selectors/transaction/meta.spec.ts create mode 100644 spec/selectors/transaction/network.spec.ts create mode 100644 spec/selectors/transaction/sign.spec.ts diff --git a/common/selectors/transaction/transaction.ts b/common/selectors/transaction/transaction.ts index f09d5e0e..b4d42490 100644 --- a/common/selectors/transaction/transaction.ts +++ b/common/selectors/transaction/transaction.ts @@ -56,12 +56,7 @@ const nonStandardTransaction = (state: AppState): boolean => { const getGasCost = (state: AppState) => { const gasPrice = getGasPrice(state); const gasLimit = getGasLimit(state); - if (!gasLimit.value) { - return Wei('0'); - } - const cost = gasLimit.value.mul(gasPrice.value); - - return cost; + return gasLimit.value ? gasPrice.value.mul(gasLimit.value) : Wei('0'); }; const serializedAndTransactionFieldsMatch = (state: AppState, isLocallySigned: boolean) => { diff --git a/package.json b/package.json index 4d725a31..bc7323bf 100644 --- a/package.json +++ b/package.json @@ -147,14 +147,10 @@ "prebuild": "check-node-version --package", "build:downloadable": "webpack --config webpack_config/webpack.html.js", "prebuild:downloadable": "check-node-version --package", - "build:electron": - "webpack --config webpack_config/webpack.electron-prod.js && node webpack_config/buildElectron.js", - "build:electron:osx": - "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=osx node webpack_config/buildElectron.js", - "build:electron:windows": - "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=windows node webpack_config/buildElectron.js", - "build:electron:linux": - "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=linux node webpack_config/buildElectron.js", + "build:electron": "webpack --config webpack_config/webpack.electron-prod.js && node webpack_config/buildElectron.js", + "build:electron:osx": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=osx node webpack_config/buildElectron.js", + "build:electron:windows": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=windows node webpack_config/buildElectron.js", + "build:electron:linux": "webpack --config webpack_config/webpack.electron-prod.js && ELECTRON_OS=linux node webpack_config/buildElectron.js", "prebuild:electron": "check-node-version --package", "test:coverage": "jest --config=jest_config/jest.config.json --coverage", "test": "jest --config=jest_config/jest.config.json", @@ -166,18 +162,14 @@ "predev": "check-node-version --package", "dev:https": "HTTPS=true node webpack_config/devServer.js", "predev:https": "check-node-version --package", - "dev:electron": - "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true node webpack_config/devServer.js' 'webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'", - "dev:electron:https": - "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true HTTPS=true node webpack_config/devServer.js' 'HTTPS=true webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'", + "dev:electron": "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true node webpack_config/devServer.js' 'webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'", + "dev:electron:https": "concurrently --kill-others --names 'webpack,electron' 'BUILD_ELECTRON=true HTTPS=true node webpack_config/devServer.js' 'HTTPS=true webpack --config webpack_config/webpack.electron-dev.js && electron dist/electron-js/main.js'", "tslint": "tslint --project . --exclude common/vendor/**/*", "tscheck": "tsc --noEmit", "start": "npm run dev", "precommit": "lint-staged", - "formatAll": - "find ./common/ -name '*.ts*' | xargs prettier --write --config ./.prettierrc --config-precedence file-override", - "prettier:diff": - "prettier --write --config ./.prettierrc --list-different \"common/**/*.ts\" \"common/**/*.tsx\"", + "formatAll": "find ./common/ -name '*.ts*' | xargs prettier --write --config ./.prettierrc --config-precedence file-override", + "prettier:diff": "prettier --write --config ./.prettierrc --list-different \"common/**/*.ts\" \"common/**/*.tsx\"", "prepush": "npm run tslint && npm run tscheck" }, "lint-staged": { diff --git a/spec/reducers/transaction/broadcast/broadcast.spec.ts b/spec/reducers/transaction/broadcast/broadcast.spec.ts new file mode 100644 index 00000000..d541d6ae --- /dev/null +++ b/spec/reducers/transaction/broadcast/broadcast.spec.ts @@ -0,0 +1,56 @@ +import { INITIAL_STATE } from 'reducers/transaction'; +import { broadcast, ITransactionStatus } from 'reducers/transaction/broadcast'; +import * as txActions from 'actions/transaction'; + +const indexingHash = 'testingHash'; + +describe('broadcast reducer', () => { + const serializedTransaction = new Buffer('testSerialized'); + const nextTxStatus: ITransactionStatus = { + broadcastedHash: null, + broadcastSuccessful: false, + isBroadcasting: true, + serializedTransaction + }; + const nextState: any = { + ...INITIAL_STATE, + [indexingHash]: nextTxStatus + }; + it('should handle BROADCAST_TRANSACTION_QUEUED', () => { + expect( + broadcast( + INITIAL_STATE as any, + txActions.broadcastTransactionQueued({ indexingHash, serializedTransaction }) + ) + ).toEqual(nextState); + }); + + it('should handle BROADCAST_TRANSACTION_SUCCESS', () => { + const broadcastedHash = 'testBroadcastHash'; + const broadcastedState = { + ...nextState, + [indexingHash]: { + ...nextTxStatus, + broadcastedHash, + isBroadcasting: false, + broadcastSuccessful: true + } + }; + expect( + broadcast( + nextState, + txActions.broadcastTransactionSucceeded({ indexingHash, broadcastedHash }) + ) + ).toEqual(broadcastedState); + }); + + it('should handle BROADCAST_TRANSACTION_FAILURE', () => { + const failedBroadcastState = { + ...nextState, + [indexingHash]: { ...nextTxStatus, isBroadcasting: false, broadcastSuccessful: false } + }; + expect(broadcast(nextState, txActions.broadcastTransactionFailed({ indexingHash }))).toEqual( + failedBroadcastState + ); + }); +}); diff --git a/spec/reducers/transaction/fields/fields.spec.ts b/spec/reducers/transaction/fields/fields.spec.ts new file mode 100644 index 00000000..c3380362 --- /dev/null +++ b/spec/reducers/transaction/fields/fields.spec.ts @@ -0,0 +1,122 @@ +import { TypeKeys } from 'actions/transaction/constants'; +import { gasPricetoBase } from 'libs/units'; +import { fields, State } from 'reducers/transaction/fields'; +import * as txActions from 'actions/transaction'; +import BN from 'bn.js'; + +describe('fields reducer', () => { + const INITIAL_STATE: State = { + to: { raw: '', value: null }, + data: { raw: '', value: null }, + nonce: { raw: '', value: null }, + value: { raw: '', value: null }, + gasLimit: { raw: '21000', value: new BN(21000) }, + gasPrice: { raw: '20', value: gasPricetoBase(20) } + }; + const testPayload = { raw: 'test', value: null }; + + it('should handle TO_FIELD_SET', () => { + expect(fields(INITIAL_STATE, txActions.setToField(testPayload))).toEqual({ + ...INITIAL_STATE, + to: testPayload + }); + }); + + it('should handle VALUE_FIELD_SET', () => { + expect(fields(INITIAL_STATE, txActions.setValueField(testPayload))).toEqual({ + ...INITIAL_STATE, + value: testPayload + }); + }); + + it('should handle DATA_FIELD_SET', () => { + expect(fields(INITIAL_STATE, txActions.setDataField(testPayload))).toEqual({ + ...INITIAL_STATE, + data: testPayload + }); + }); + + it('should handle GAS_LIMIT_FIELD_SET', () => { + expect(fields(INITIAL_STATE, txActions.setGasLimitField(testPayload))).toEqual({ + ...INITIAL_STATE, + gasLimit: testPayload + }); + }); + + it('should handle NONCE_SET', () => { + expect(fields(INITIAL_STATE, txActions.setNonceField(testPayload))).toEqual({ + ...INITIAL_STATE, + nonce: testPayload + }); + }); + + it('should handle GAS_PRICE_FIELD_SET', () => { + expect(fields(INITIAL_STATE, txActions.setGasPriceField(testPayload))).toEqual({ + ...INITIAL_STATE, + gasPrice: testPayload + }); + }); + + it('should handle TOKEN_TO_ETHER_SWAP', () => { + const swapAction: txActions.SwapTokenToEtherAction = { + type: TypeKeys.TOKEN_TO_ETHER_SWAP, + payload: { + to: testPayload, + value: testPayload, + decimal: 1 + } + }; + expect(fields(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + to: testPayload, + value: testPayload + }); + }); + + it('should handle ETHER_TO_TOKEN_SWAP', () => { + const swapAction: txActions.SwapEtherToTokenAction = { + type: TypeKeys.ETHER_TO_TOKEN_SWAP, + payload: { + to: testPayload, + data: testPayload, + tokenTo: testPayload, + tokenValue: testPayload, + decimal: 1 + } + }; + expect(fields(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + to: testPayload, + data: testPayload + }); + }); + + it('should handle TOKEN_TO_TOKEN_SWAP', () => { + const swapAction: txActions.SwapTokenToTokenAction = { + type: TypeKeys.TOKEN_TO_TOKEN_SWAP, + payload: { + to: testPayload, + data: testPayload, + tokenValue: testPayload, + decimal: 1 + } + }; + expect(fields(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + to: testPayload, + data: testPayload + }); + }); + + it('should reset', () => { + const resetAction: txActions.ResetAction = { + type: TypeKeys.RESET, + payload: { include: {}, exclude: {} } + }; + const modifiedState: State = { + ...INITIAL_STATE, + data: { raw: 'modified', value: null } + }; + expect(fields(modifiedState, resetAction)).toEqual(INITIAL_STATE); + }); +}); diff --git a/spec/reducers/transaction/meta/meta.spec.ts b/spec/reducers/transaction/meta/meta.spec.ts new file mode 100644 index 00000000..57d494ab --- /dev/null +++ b/spec/reducers/transaction/meta/meta.spec.ts @@ -0,0 +1,109 @@ +import { TypeKeys } from 'actions/transaction/constants'; +import { getDecimalFromEtherUnit } from 'libs/units'; +import { State, meta } from 'reducers/transaction/meta'; +import * as txActions from 'actions/transaction'; + +describe('meta reducer', () => { + const INITIAL_STATE: State = { + unit: '', + previousUnit: '', + decimal: getDecimalFromEtherUnit('ether'), + tokenValue: { raw: '', value: null }, + tokenTo: { raw: '', value: null }, + from: null + }; + + const testPayload = { raw: 'test', value: null }; + + it('should handle UNIT_META_SET', () => { + const setUnitMetaAction: txActions.SetUnitMetaAction = { + type: TypeKeys.UNIT_META_SET, + payload: 'test' + }; + expect(meta(INITIAL_STATE, setUnitMetaAction)); + }); + + it('should handle TOKEN_VALUE_META_SET', () => { + expect(meta(INITIAL_STATE, txActions.setTokenValue(testPayload))).toEqual({ + ...INITIAL_STATE, + tokenValue: testPayload + }); + }); + + it('should handle TOKEN_TO_META_SET', () => { + expect(meta(INITIAL_STATE, txActions.setTokenTo(testPayload))).toEqual({ + ...INITIAL_STATE, + tokenTo: testPayload + }); + }); + + it('should handle GET_FROM_SUCCEEDED', () => { + expect(meta(INITIAL_STATE, txActions.getFromSucceeded('test'))).toEqual({ + ...INITIAL_STATE, + from: 'test' + }); + }); + + it('should handle TOKEN_TO_ETHER_SWAP', () => { + const swapAction: txActions.SwapTokenToEtherAction = { + type: TypeKeys.TOKEN_TO_ETHER_SWAP, + payload: { + to: testPayload, + value: testPayload, + decimal: 1 + } + }; + expect(meta(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + decimal: swapAction.payload.decimal + }); + }); + + it('should handle ETHER_TO_TOKEN_SWAP', () => { + const swapAction: txActions.SwapEtherToTokenAction = { + type: TypeKeys.ETHER_TO_TOKEN_SWAP, + payload: { + to: testPayload, + data: testPayload, + tokenTo: testPayload, + tokenValue: testPayload, + decimal: 1 + } + }; + expect(meta(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + decimal: swapAction.payload.decimal, + tokenTo: testPayload, + tokenValue: testPayload + }); + }); + + it('should handle TOKEN_TO_TOKEN_SWAP', () => { + const swapAction: txActions.SwapTokenToTokenAction = { + type: TypeKeys.TOKEN_TO_TOKEN_SWAP, + payload: { + to: testPayload, + data: testPayload, + tokenValue: testPayload, + decimal: 1 + } + }; + expect(meta(INITIAL_STATE, swapAction)).toEqual({ + ...INITIAL_STATE, + decimal: swapAction.payload.decimal, + tokenValue: testPayload + }); + }); + + it('should reset', () => { + const resetAction: txActions.ResetAction = { + type: TypeKeys.RESET, + payload: { include: {}, exclude: {} } + }; + const modifiedState: State = { + ...INITIAL_STATE, + unit: 'modified' + }; + expect(meta(modifiedState, resetAction)).toEqual(INITIAL_STATE); + }); +}); diff --git a/spec/reducers/transaction/network/network.spec.ts b/spec/reducers/transaction/network/network.spec.ts new file mode 100644 index 00000000..0a1839a7 --- /dev/null +++ b/spec/reducers/transaction/network/network.spec.ts @@ -0,0 +1,55 @@ +import { State, network } from 'reducers/transaction/network'; +import * as txActions from 'actions/transaction'; +import { TypeKeys } from 'actions/transaction/constants'; + +describe('network reducer', () => { + const INITIAL_STATE: State = { + gasEstimationStatus: null, + getFromStatus: null, + getNonceStatus: null, + gasPriceStatus: null + }; + + it('should handle gas estimation status actions', () => { + const gasEstimationAction: txActions.NetworkAction = { + type: TypeKeys.ESTIMATE_GAS_SUCCEEDED + }; + expect(network(INITIAL_STATE, gasEstimationAction)).toEqual({ + ...INITIAL_STATE, + gasEstimationStatus: 'SUCCESS' + }); + }); + + it('should handle get from status actions', () => { + const getFromAction: txActions.NetworkAction = { + type: TypeKeys.GET_FROM_SUCCEEDED, + payload: 'test' + }; + expect(network(INITIAL_STATE, getFromAction)).toEqual({ + ...INITIAL_STATE, + getFromStatus: 'SUCCESS' + }); + }); + + it('should handle get nonce status actions', () => { + const getNonceAction: txActions.NetworkAction = { + type: TypeKeys.GET_NONCE_SUCCEEDED, + payload: 'test' + }; + expect(network(INITIAL_STATE, getNonceAction)).toEqual({ + ...INITIAL_STATE, + getNonceStatus: 'SUCCESS' + }); + }); + + it('should handle gasPriceIntent', () => { + const gasPriceAction: txActions.InputGasPriceAction = { + type: TypeKeys.GAS_PRICE_INPUT, + payload: 'test' + }; + expect(network(INITIAL_STATE, gasPriceAction)).toEqual({ + ...INITIAL_STATE, + gasPriceStatus: 'SUCCESS' + }); + }); +}); diff --git a/spec/reducers/transaction/signing/sign.spec.ts b/spec/reducers/transaction/signing/sign.spec.ts new file mode 100644 index 00000000..a190e1b6 --- /dev/null +++ b/spec/reducers/transaction/signing/sign.spec.ts @@ -0,0 +1,62 @@ +import EthTx from 'ethereumjs-tx'; +import * as txActions from 'actions/transaction'; +import { TypeKeys } from 'actions/transaction/constants'; +import { State, sign } from 'reducers/transaction/sign'; + +describe('sign reducer', () => { + const INITIAL_STATE: State = { + local: { signedTransaction: null }, + web3: { transaction: null }, + indexingHash: null, + pending: false + }; + it('should handle SIGN_TRANSACTION_REQUESTED', () => { + const signTxRequestedAction: txActions.SignTransactionRequestedAction = { + type: TypeKeys.SIGN_TRANSACTION_REQUESTED, + payload: {} as EthTx + }; + expect(sign(INITIAL_STATE, signTxRequestedAction)).toEqual({ ...INITIAL_STATE, pending: true }); + }); + + it('should handle SIGN_LOCAL_TRANSACTION_SUCCEEDED', () => { + const signedTransaction = new Buffer('test'); + const indexingHash = 'test'; + const signLocalTxSucceededAction: txActions.SignLocalTransactionSucceededAction = { + type: TypeKeys.SIGN_LOCAL_TRANSACTION_SUCCEEDED, + payload: { signedTransaction, indexingHash } + }; + expect(sign(INITIAL_STATE, signLocalTxSucceededAction)).toEqual({ + ...INITIAL_STATE, + pending: false, + indexingHash, + local: { signedTransaction } + }); + }); + + it('should handle SIGN_WEB3_TRANSACTION_SUCCEEDED', () => { + const transaction = new Buffer('test'); + const indexingHash = 'test'; + const signWeb3TxSucceededAction: txActions.SignWeb3TransactionSucceededAction = { + type: TypeKeys.SIGN_WEB3_TRANSACTION_SUCCEEDED, + payload: { transaction, indexingHash } + }; + expect(sign(INITIAL_STATE, signWeb3TxSucceededAction)).toEqual({ + ...INITIAL_STATE, + pending: false, + indexingHash, + web3: { transaction } + }); + }); + + it('should reset', () => { + const resetAction: txActions.ResetAction = { + type: TypeKeys.RESET, + payload: { include: {}, exclude: {} } + }; + const modifiedState: State = { + ...INITIAL_STATE, + pending: true + }; + expect(sign(modifiedState, resetAction)).toEqual(INITIAL_STATE); + }); +}); diff --git a/spec/sagas/transaction/current/currentTo.spec.ts b/spec/sagas/transaction/current/currentTo.spec.ts index e8c3e4fa..08ce23aa 100644 --- a/spec/sagas/transaction/current/currentTo.spec.ts +++ b/spec/sagas/transaction/current/currentTo.spec.ts @@ -1,46 +1,97 @@ -import { configuredStore } from 'store'; +import { getResolvedAddress } from 'selectors/ens'; import { Address } from 'libs/units'; -import { call, select, put } from 'redux-saga/effects'; +import { call, select, put, take } from 'redux-saga/effects'; import { isValidETHAddress, isValidENSAddress } from 'libs/validators'; import { setCurrentTo, setField } from 'sagas/transaction/current/currentTo'; import { isEtherTransaction } from 'selectors/transaction'; import { cloneableGenerator } from 'redux-saga/utils'; import { setToField, setTokenTo } from 'actions/transaction'; -configuredStore.getState(); -const raw = '0xa'; - -const payload = { - raw, - value: Address(raw) -}; +import { resolveDomainRequested, TypeKeys as ENSTypekeys } from 'actions/ens'; describe('setCurrentTo*', () => { - const action: any = { - payload: raw - }; - const validAddress = true; - const validEns = false; + const data = {} as any; - const gen = setCurrentTo(action); + describe('with valid Ethereum address', () => { + const raw = '0xa'; + const ethAddrPayload = { + raw, + value: Address(raw) + }; + const ethAddrAction: any = { + payload: raw + }; - it('should call isValidETHAddress', () => { - expect(gen.next().value).toEqual(call(isValidETHAddress, raw)); + data.validEthGen = setCurrentTo(ethAddrAction); + it('should call isValidETHAddress', () => { + expect(data.validEthGen.next().value).toEqual(call(isValidETHAddress, raw)); + }); + + it('should call isValidENSAddress', () => { + expect(data.validEthGen.next(raw).value).toEqual(call(isValidENSAddress, raw)); + }); + + it('should call setField', () => { + expect(data.validEthGen.next(raw).value).toEqual(call(setField, ethAddrPayload)); + }); }); - it('should call isValidENSAddress', () => { - expect(gen.next(validAddress).value).toEqual(call(isValidENSAddress, raw)); - }); + describe('with invalid Ethereum address, valid ENS address', () => { + const raw = 'testing.eth'; + const resolvedAddress = '0xa'; + const [domain] = raw.split('.'); + const ensAddrPayload = { + raw, + value: null + }; + const ensAddrAction: any = { + payload: raw + }; + data.validEnsGen = setCurrentTo(ensAddrAction); - it('should call setField', () => { - expect(gen.next(validEns).value).toEqual(call(setField, payload)); - }); + it('should call isValidETHAddress', () => { + expect(data.validEnsGen.next().value).toEqual(call(isValidETHAddress, raw)); + }); - it('should be done', () => { - expect(gen.next().done).toEqual(true); + it('should call isValidENSAddress', () => { + expect(data.validEnsGen.next(false).value).toEqual(call(isValidENSAddress, raw)); + }); + + it('should call setField', () => { + expect(data.validEnsGen.next(true).value).toEqual(call(setField, ensAddrPayload)); + }); + + it('should put resolveDomainRequested', () => { + expect(data.validEnsGen.next().value).toEqual(put(resolveDomainRequested(domain))); + }); + + it('should take ENS type keys', () => { + expect(data.validEnsGen.next().value).toEqual( + take([ + ENSTypekeys.ENS_RESOLVE_DOMAIN_FAILED, + ENSTypekeys.ENS_RESOLVE_DOMAIN_SUCCEEDED, + ENSTypekeys.ENS_RESOLVE_DOMAIN_CACHED + ]) + ); + }); + + it('should select getResolvedAddress', () => { + expect(data.validEnsGen.next().value).toEqual(select(getResolvedAddress, true)); + }); + + it('should call setField', () => { + expect(data.validEnsGen.next(resolvedAddress).value).toEqual( + call(setField, { raw, value: Address(resolvedAddress) }) + ); + }); }); }); describe('setField', () => { + const raw = '0xa'; + const payload = { + raw, + value: Address(raw) + }; const etherTransaction = cloneableGenerator(setField)(payload); it('should select etherTransaction', () => { expect(etherTransaction.next().value).toEqual(select(isEtherTransaction)); diff --git a/spec/sagas/transaction/current/currentValue.spec.ts b/spec/sagas/transaction/current/currentValue.spec.ts index 11878b37..58216530 100644 --- a/spec/sagas/transaction/current/currentValue.spec.ts +++ b/spec/sagas/transaction/current/currentValue.spec.ts @@ -46,7 +46,6 @@ describe('valueHandler', () => { }); it('should select getUnit', () => { gen.invalidDecimal = gen.pass.clone(); - expect(gen.pass.next(decimal).value).toEqual(select(getUnit)); expect(gen.invalidNumber.next(decimal).value).toEqual(select(getUnit)); expect(gen.invalidDecimal.next(failCases.invalidDecimal).value).toEqual(select(getUnit)); diff --git a/spec/sagas/transaction/fields/fields.spec.ts b/spec/sagas/transaction/fields/fields.spec.ts index 561a85d3..414b3492 100644 --- a/spec/sagas/transaction/fields/fields.spec.ts +++ b/spec/sagas/transaction/fields/fields.spec.ts @@ -1,5 +1,5 @@ -import { configuredStore } from 'store'; import BN from 'bn.js'; +import { SagaIterator, delay } from 'redux-saga'; import { call, put } from 'redux-saga/effects'; import { setDataField, setGasLimitField, setNonceField } from 'actions/transaction/actionCreators'; import { isValidHex, isValidNonce, gasPriceValidator, gasLimitValidator } from 'libs/validators'; @@ -8,12 +8,11 @@ import { handleDataInput, handleGasLimitInput, handleNonceInput, - handleGasPriceInput + handleGasPriceInput, + handleGasPriceInputIntent } from 'sagas/transaction/fields/fields'; import { cloneableGenerator } from 'redux-saga/utils'; -import { setGasPriceField } from 'actions/transaction'; -import { SagaIterator } from 'redux-saga'; -configuredStore.getState(); +import { setGasPriceField, inputGasPrice } from 'actions/transaction'; const itShouldBeDone = (gen: SagaIterator) => { it('should be done', () => { @@ -142,6 +141,19 @@ describe('handleGasPriceInput*', () => { }); }); +describe('handleGasPriceInputIntent*', () => { + const payload = '100.111'; + const action: any = { payload }; + const gen = handleGasPriceInputIntent(action); + it('should call delay', () => { + expect(gen.next().value).toEqual(call(delay, 300)); + }); + + it('should put inputGasPrice', () => { + expect(gen.next().value).toEqual(put(inputGasPrice(payload))); + }); +}); + describe('handleNonceInput*', () => { const payload = '42'; const action: any = { payload }; diff --git a/spec/sagas/transaction/sign/signing.spec.ts b/spec/sagas/transaction/sign/signing.spec.ts new file mode 100644 index 00000000..fe5e180c --- /dev/null +++ b/spec/sagas/transaction/sign/signing.spec.ts @@ -0,0 +1,79 @@ +import { put, apply, call } from 'redux-saga/effects'; +import { signLocalTransactionSucceeded, signWeb3TransactionSucceeded } from 'actions/transaction'; +import { computeIndexingHash } from 'libs/transaction'; +import { + signLocalTransactionHandler, + signWeb3TransactionHandler +} from 'sagas/transaction/signing/signing'; + +describe('signLocalTransactionHandler*', () => { + const tx = 'tx'; + const wallet = { + signRawTransaction: jest.fn() + }; + const action: any = { tx, wallet }; + const signedTransaction = new Buffer('signedTransaction'); + const indexingHash = 'indexingHash'; + + const gen = signLocalTransactionHandler(action); + + it('should apply wallet.signRawTransaction', () => { + expect(gen.next().value).toEqual(apply(wallet, wallet.signRawTransaction, [tx])); + }); + + it('should call computeIndexingHash', () => { + expect(gen.next(signedTransaction).value).toEqual(call(computeIndexingHash, signedTransaction)); + }); + + it('should put signLocalTransactionSucceeded', () => { + expect(gen.next(indexingHash).value).toEqual( + put( + signLocalTransactionSucceeded({ + signedTransaction, + indexingHash, + noVerify: false + }) + ) + ); + }); + + it('should be done', () => { + expect(gen.next().done).toEqual(true); + }); +}); + +describe('signWeb3TransactionHandler*', () => { + const tx = { + serialize: jest.fn + }; + const action: any = { tx }; + const serializedTransaction = new Buffer('tx'); + const indexingHash = 'indexingHash'; + + const gen = signWeb3TransactionHandler(action); + + it('should apply tx.serialize', () => { + expect(gen.next().value).toEqual(apply(tx, tx.serialize)); + }); + + it('should call computeIndexingHash', () => { + expect(gen.next(serializedTransaction).value).toEqual( + call(computeIndexingHash, serializedTransaction) + ); + }); + + it('should put signWeb3TransactionSucceeded', () => { + expect(gen.next(indexingHash).value).toEqual( + put( + signWeb3TransactionSucceeded({ + transaction: serializedTransaction, + indexingHash + }) + ) + ); + }); + + it('should be done', () => { + expect(gen.next().done).toEqual(true); + }); +}); diff --git a/spec/selectors/helpers.ts b/spec/selectors/helpers.ts new file mode 100644 index 00000000..16d4fe43 --- /dev/null +++ b/spec/selectors/helpers.ts @@ -0,0 +1,11 @@ +import { configuredStore } from '../../common/store'; + +export function getInitialState() { + return { ...configuredStore.getState() }; +} + +export function testShallowlyEqual(oldValue: any, newValue: any) { + it('should be shallowly equal when called again with the same state', () => { + expect(oldValue === newValue).toBeTruthy(); + }); +} diff --git a/spec/selectors/transaction/broadcast.spec.ts b/spec/selectors/transaction/broadcast.spec.ts new file mode 100644 index 00000000..29a150b8 --- /dev/null +++ b/spec/selectors/transaction/broadcast.spec.ts @@ -0,0 +1,63 @@ +import { + getTransactionStatus, + currentTransactionFailed, + currentTransactionBroadcasting, + currentTransactionBroadcasted, + getCurrentTransactionStatus +} from 'selectors/transaction'; +import { getInitialState } from '../helpers'; + +describe('broadcast selector', () => { + const state = getInitialState(); + state.transaction = { + ...state.transaction, + broadcast: { + ...state.transaction.broadcast, + testIndexingHash1: { + broadcastedHash: 'testBroadcastedHash', + broadcastSuccessful: true, + isBroadcasting: false, + serializedTransaction: new Buffer([1, 2, 3]) + }, + testIndexingHash2: { + broadcastedHash: 'testBroadcastedHash', + broadcastSuccessful: true, + isBroadcasting: false, + serializedTransaction: new Buffer([1, 2, 3]) + } + }, + sign: { + ...state.transaction.sign, + indexingHash: 'testIndexingHash1', + pending: false + } + }; + it('should check getTransactionState with an indexing hash', () => { + expect(getTransactionStatus(state, 'testIndexingHash1')).toEqual( + state.transaction.broadcast.testIndexingHash1 + ); + }); + + it('should check getCurrentTransactionStatus', () => { + expect(getCurrentTransactionStatus(state)).toEqual( + state.transaction.broadcast.testIndexingHash2 + ); + }); + + it('should check currentTransactionFailed', () => { + expect(currentTransactionFailed(state)).toEqual(false); + }); + + it('should check currentTransactionBroadcasting', () => { + expect(currentTransactionBroadcasting(state)).toEqual(false); + }); + + it('should check currentTransactionBroadcasted', () => { + expect(currentTransactionBroadcasted(state)).toEqual(true); + }); + + it('should return false on getCurrentTransactionStatus if no index hash present', () => { + state.transaction.sign.indexingHash = null; + expect(getCurrentTransactionStatus(state)).toEqual(false); + }); +}); diff --git a/spec/selectors/transaction/current.spec.ts b/spec/selectors/transaction/current.spec.ts new file mode 100644 index 00000000..54c7b3ec --- /dev/null +++ b/spec/selectors/transaction/current.spec.ts @@ -0,0 +1,68 @@ +import { Wei } from 'libs/units'; +import { + getCurrentValue, + getCurrentTo, + isEtherTransaction, + isValidCurrentTo, + isValidGasPrice, + isValidGasLimit, + getCurrentToAddressMessage +} from 'selectors/transaction'; +import { getInitialState } from '../helpers'; + +describe('current selector', () => { + const state = getInitialState(); + state.transaction = { + ...state.transaction, + fields: { + ...state.transaction.fields, + to: { + raw: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520', + value: new Buffer([0, 1, 2, 3]) + }, + gasLimit: { + raw: '21000', + value: Wei('21000') + }, + gasPrice: { + raw: '1500', + value: Wei('1500') + } + }, + meta: { + ...state.transaction.meta, + unit: 'ETH', + previousUnit: 'ETH' + } + }; + + it('should get stored receiver address on getCurrentTo', () => { + expect(getCurrentTo(state)).toEqual(state.transaction.fields.to); + }); + + it('should get stored value on getCurrentValue', () => { + expect(getCurrentValue(state)).toEqual(state.transaction.fields.value); + }); + + it('should get message to the receiver', () => { + expect(getCurrentToAddressMessage(state)).toEqual({ + msg: 'Thank you for donating to MyCrypto. TO THE MOON!' + }); + }); + + it('should check isValidGasPrice', () => { + expect(isValidGasPrice(state)).toEqual(true); + }); + + it('should check isEtherTransaction', () => { + expect(isEtherTransaction(state)).toEqual(true); + }); + + it('should check isValidGasLimit', () => { + expect(isValidGasLimit(state)).toEqual(true); + }); + + it('should check isValidCurrentTo', () => { + expect(isValidCurrentTo(state)).toEqual(true); + }); +}); diff --git a/spec/selectors/transaction/fields.spec.ts b/spec/selectors/transaction/fields.spec.ts new file mode 100644 index 00000000..9cd87b2e --- /dev/null +++ b/spec/selectors/transaction/fields.spec.ts @@ -0,0 +1,88 @@ +import BN from 'bn.js'; +import { Wei } from 'libs/units'; +import { + getData, + getFields, + getGasLimit, + getValue, + getTo, + getNonce, + getGasPrice, + getDataExists, + getValidGasCost +} from 'selectors/transaction'; +import { getInitialState } from '../helpers'; + +describe('fields selector', () => { + const state = getInitialState(); + state.transaction.fields = { + to: { + raw: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520', + value: new Buffer([0, 1, 2, 3]) + }, + data: { + raw: '', + value: null + }, + nonce: { + raw: '0', + value: new BN('0') + }, + value: { + raw: '1000000000', + value: Wei('1000000000') + }, + gasLimit: { + raw: '21000', + value: Wei('21000') + }, + gasPrice: { + raw: '1500', + value: Wei('1500') + } + }; + + it('should get fields from fields store', () => { + expect(getFields(state)).toEqual(state.transaction.fields); + }); + + it('should get data from fields store', () => { + expect(getData(state)).toEqual(state.transaction.fields.data); + }); + + it('should get gas limit from fields store', () => { + expect(getGasLimit(state)).toEqual(state.transaction.fields.gasLimit); + }); + + it('should get value from fields store', () => { + expect(getValue(state)).toEqual(state.transaction.fields.value); + }); + + it('sould get receiver address from fields store', () => { + expect(getTo(state)).toEqual(state.transaction.fields.to); + }); + + it('should get nonce from fields store', () => { + expect(getNonce(state)).toEqual(state.transaction.fields.nonce); + }); + + it('should get gas price from fields store', () => { + expect(getGasPrice(state)).toEqual(state.transaction.fields.gasPrice); + }); + + it('should check getDataExists', () => { + expect(getDataExists(state)).toEqual(false); + }); + + it('should check when gas cost is valid', () => { + expect(getValidGasCost(state)).toEqual(true); + }); + + it('should check when gas cost is invalid', () => { + state.wallet.balance = { + wei: Wei('0'), + isPending: false + }; + expect(getValidGasCost(state)).toEqual(false); + }); +}); diff --git a/spec/selectors/transaction/helpers.spec.ts b/spec/selectors/transaction/helpers.spec.ts new file mode 100644 index 00000000..c4141052 --- /dev/null +++ b/spec/selectors/transaction/helpers.spec.ts @@ -0,0 +1,99 @@ +import BN from 'bn.js'; +import { Wei } from 'libs/units'; +import { reduceToValues, isFullTx } from 'selectors/transaction/helpers'; +import { + getCurrentTo, + getCurrentValue, + getFields, + getUnit, + getDataExists, + getValidGasCost +} from 'selectors/transaction'; +import { getInitialState } from '../helpers'; + +describe('helpers selector', () => { + const state = getInitialState(); + state.transaction = { + ...state.transaction, + meta: { + ...state.transaction.meta, + unit: 'ETH' + }, + fields: { + to: { + raw: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520', + value: new Buffer([0, 1, 2, 3]) + }, + data: { + raw: '', + value: null + }, + nonce: { + raw: '0', + value: new BN('0') + }, + value: { + raw: '1000000000', + value: Wei('1000000000') + }, + gasLimit: { + raw: '21000', + value: Wei('21000') + }, + gasPrice: { + raw: '1500', + value: Wei('1500') + } + } + }; + + it('should reduce the fields state to its base values', () => { + const values = { + data: null, + gasLimit: Wei('21000'), + gasPrice: Wei('1500'), + nonce: new BN('0'), + to: new Buffer([0, 1, 2, 3]), + value: Wei('1000000000') + }; + expect(reduceToValues(state.transaction.fields)).toEqual(values); + }); + + it('should check isFullTransaction with full transaction arguments', () => { + const currentTo = getCurrentTo(state); + const currentValue = getCurrentValue(state); + const transactionFields = getFields(state); + const unit = getUnit(state); + const dataExists = getDataExists(state); + const validGasCost = getValidGasCost(state); + const isFullTransaction = isFullTx( + state, + transactionFields, + currentTo, + currentValue, + dataExists, + validGasCost, + unit + ); + expect(isFullTransaction).toEqual(true); + }); + + it('should check isFullTransaction without full transaction arguments', () => { + const currentTo = { raw: '', value: null }; + const currentValue = getCurrentValue(state); + const transactionFields = getFields(state); + const unit = getUnit(state); + const dataExists = getDataExists(state); + const validGasCost = getValidGasCost(state); + const isFullTransaction = isFullTx( + state, + transactionFields, + currentTo, + currentValue, + dataExists, + validGasCost, + unit + ); + expect(isFullTransaction).toEqual(false); + }); +}); diff --git a/spec/selectors/transaction/meta.spec.ts b/spec/selectors/transaction/meta.spec.ts new file mode 100644 index 00000000..1c98760a --- /dev/null +++ b/spec/selectors/transaction/meta.spec.ts @@ -0,0 +1,70 @@ +import { + getFrom, + getDecimal, + getTokenValue, + getTokenTo, + getUnit, + getPreviousUnit, + getDecimalFromUnit +} from 'selectors/transaction/meta'; +import { getInitialState } from '../helpers'; + +describe('meta tests', () => { + const state = getInitialState(); + (state.transaction.meta = { + unit: 'ETH', + previousUnit: 'ETH', + decimal: 18, + tokenValue: { + raw: '', + value: null + }, + tokenTo: { + raw: '', + value: null + }, + from: 'fromAddress' + }), + (state.customTokens = [ + { + address: '0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7', + symbol: 'UNI', + decimal: 0 + } + ]); + it('should get the stored sender address', () => { + expect(getFrom(state)).toEqual(state.transaction.meta.from); + }); + + it('should get the stored decimal', () => { + expect(getDecimal(state)).toEqual(state.transaction.meta.decimal); + }); + + it('should get the token value', () => { + expect(getTokenValue(state)).toEqual(state.transaction.meta.tokenValue); + }); + + it('should get the token receiver address', () => { + expect(getTokenTo(state)).toEqual(state.transaction.meta.tokenTo); + }); + + it('should get the stored unit', () => { + expect(getUnit(state)).toEqual(state.transaction.meta.unit); + }); + + it('should get the stored previous unit', () => { + expect(getPreviousUnit(state)).toEqual(state.transaction.meta.previousUnit); + }); + + it('should get the decimal for ether', () => { + expect(getDecimalFromUnit(state, getUnit(state))).toEqual(18); + }); + + it('should get the decimal for a token', () => { + expect(getDecimalFromUnit(state, 'UNI')).toEqual(0); + }); + + it('should throw error if the token is not found', () => { + expect(() => getDecimalFromUnit(state, 'ABC')).toThrowError(`Token ABC not found`); + }); +}); diff --git a/spec/selectors/transaction/network.spec.ts b/spec/selectors/transaction/network.spec.ts new file mode 100644 index 00000000..31e59cef --- /dev/null +++ b/spec/selectors/transaction/network.spec.ts @@ -0,0 +1,48 @@ +import { RequestStatus } from 'reducers/transaction/network'; +import { + getNetworkStatus, + nonceRequestPending, + nonceRequestFailed, + isNetworkRequestPending, + getGasEstimationPending, + getGasLimitEstimationTimedOut +} from 'selectors/transaction'; +import { getInitialState } from '../helpers'; + +describe('current selector', () => { + const state = getInitialState(); + state.transaction.network = { + ...state.transaction.network, + gasEstimationStatus: RequestStatus.REQUESTED, + getFromStatus: RequestStatus.SUCCEEDED, + getNonceStatus: RequestStatus.REQUESTED, + gasPriceStatus: RequestStatus.SUCCEEDED + }; + + it('should get network status', () => { + expect(getNetworkStatus(state)).toEqual(state.transaction.network); + }); + + it('should check with the store if the nonce request is pending', () => { + expect(nonceRequestPending(state)).toEqual(true); + }); + + it('should check with the store if the nonce request failed', () => { + state.transaction.network.getNonceStatus = RequestStatus.FAILED; + expect(nonceRequestFailed(state)).toEqual(true); + }); + + it('should check with the store if the gas estimation is pending', () => { + expect(getGasEstimationPending(state)).toEqual(true); + }); + + it('should check with the store if gas limit estimation timed out', () => { + state.transaction.network.gasEstimationStatus = RequestStatus.TIMEDOUT; + expect(getGasLimitEstimationTimedOut(state)).toEqual(true); + }); + + it('should check with the store if network request is pending', () => { + state.transaction.network.gasEstimationStatus = RequestStatus.REQUESTED; + expect(isNetworkRequestPending(state)).toEqual(true); + }); +}); diff --git a/spec/selectors/transaction/sign.spec.ts b/spec/selectors/transaction/sign.spec.ts new file mode 100644 index 00000000..dd9fb1cf --- /dev/null +++ b/spec/selectors/transaction/sign.spec.ts @@ -0,0 +1,44 @@ +import { + signaturePending, + getSignedTx, + getWeb3Tx, + getSignState, + getSerializedTransaction +} from 'selectors/transaction/sign'; +import { getInitialState } from '../helpers'; + +describe('sign tests', () => { + const state = getInitialState(); + (state.transaction.sign = { + indexingHash: 'testIndexingHash', + pending: false, + local: { + signedTransaction: new Buffer([4, 5, 6, 7]) + }, + web3: { + transaction: null + } + }), + it('should return whether the current signature is pending', () => { + expect(signaturePending(state)).toEqual({ + isHardwareWallet: false, + isSignaturePending: false + }); + }); + + it('should should get the stored sign state', () => { + expect(getSignState(state)).toEqual(state.transaction.sign); + }); + + it('should get the signed local transaction state', () => { + expect(getSignedTx(state)).toEqual(state.transaction.sign.local.signedTransaction); + }); + + it('should get the signed web3 transaction state', () => { + expect(getWeb3Tx(state)).toEqual(state.transaction.sign.web3.transaction); + }); + + it('should get the serialized transaction state', () => { + expect(getSerializedTransaction(state)).toEqual(new Buffer([4, 5, 6, 7])); + }); +});