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.
This commit is contained in:
aitrean 2018-03-08 13:03:45 -05:00 committed by Daniel Ternyak
parent 94b3f3403b
commit cf9887f21f
19 changed files with 1076 additions and 53 deletions

View File

@ -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) => {

View File

@ -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": {

View File

@ -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
);
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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'
});
});
});

View File

@ -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);
});
});

View File

@ -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));

View File

@ -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));

View File

@ -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 };

View File

@ -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);
});
});

11
spec/selectors/helpers.ts Normal file
View File

@ -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();
});
}

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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`);
});
});

View File

@ -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);
});
});

View File

@ -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]));
});
});