Saga Testing (#415)
* add exports to config saga, refactor * add config saga tests * add exports to necessary files * add remaining saga test & snapshots * update orders saga spec to use Infinity constant * update dWallet saga spec snapshot * refactor config saga slightly * update config saga spec * update config saga snapshot * update rates saga spec * remove unused vars from config saga spec
This commit is contained in:
parent
31963b334c
commit
6c09e7160a
|
@ -69,14 +69,14 @@ export interface BityOrderCreateRequestedSwapAction {
|
|||
};
|
||||
}
|
||||
|
||||
interface BityOrderInput {
|
||||
export interface BityOrderInput {
|
||||
amount: string;
|
||||
currency: string;
|
||||
reference: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface BityOrderOutput {
|
||||
export interface BityOrderOutput {
|
||||
amount: string;
|
||||
currency: string;
|
||||
reference: string;
|
||||
|
|
|
@ -30,7 +30,8 @@ import {
|
|||
changeNode,
|
||||
changeNodeIntent,
|
||||
setLatestBlock,
|
||||
AddCustomNodeAction
|
||||
AddCustomNodeAction,
|
||||
ChangeNodeIntentAction
|
||||
} from 'actions/config';
|
||||
import { showNotification } from 'actions/notifications';
|
||||
import translate from 'translations';
|
||||
|
@ -100,13 +101,13 @@ export function* pollOfflineStatus(): SagaIterator {
|
|||
}
|
||||
|
||||
// Fork our recurring API call, watch for the need to cancel.
|
||||
function* handlePollOfflineStatus(): SagaIterator {
|
||||
export function* handlePollOfflineStatus(): SagaIterator {
|
||||
const pollOfflineStatusTask = yield fork(pollOfflineStatus);
|
||||
yield take('CONFIG_STOP_POLL_OFFLINE_STATE');
|
||||
yield cancel(pollOfflineStatusTask);
|
||||
}
|
||||
|
||||
function* handleTogglePollOfflineStatus(): SagaIterator {
|
||||
export function* handleTogglePollOfflineStatus(): SagaIterator {
|
||||
const isForcedOffline = yield select(getForceOffline);
|
||||
if (isForcedOffline) {
|
||||
yield fork(handlePollOfflineStatus);
|
||||
|
@ -117,14 +118,15 @@ function* handleTogglePollOfflineStatus(): SagaIterator {
|
|||
|
||||
// @HACK For now we reload the app when doing a language swap to force non-connected
|
||||
// data to reload. Also the use of timeout to avoid using additional actions for now.
|
||||
function* reload(): SagaIterator {
|
||||
export function* reload(): SagaIterator {
|
||||
setTimeout(() => location.reload(), 250);
|
||||
}
|
||||
|
||||
function* handleNodeChangeIntent(action): SagaIterator {
|
||||
export function* handleNodeChangeIntent(
|
||||
action: ChangeNodeIntentAction
|
||||
): SagaIterator {
|
||||
const currentNode = yield select(getNode);
|
||||
const currentConfig = yield select(getNodeConfig);
|
||||
const currentWallet = yield select(getWalletInst);
|
||||
const currentNetwork = currentConfig.network;
|
||||
|
||||
let actionConfig = NODES[action.payload];
|
||||
|
@ -173,6 +175,8 @@ function* handleNodeChangeIntent(action): SagaIterator {
|
|||
yield put(setLatestBlock(latestBlock));
|
||||
yield put(changeNode(action.payload, actionConfig));
|
||||
|
||||
const currentWallet = yield select(getWalletInst);
|
||||
|
||||
// if there's no wallet, do not reload as there's no component state to resync
|
||||
if (currentWallet && currentNetwork !== actionConfig.network) {
|
||||
yield call(reload);
|
||||
|
@ -185,7 +189,7 @@ export function* switchToNewNode(action: AddCustomNodeAction): SagaIterator {
|
|||
}
|
||||
|
||||
// unset web3 as the selected node if a non-web3 wallet has been selected
|
||||
function* unsetWeb3Node(action): SagaIterator {
|
||||
export function* unsetWeb3Node(action): SagaIterator {
|
||||
const node = yield select(getNode);
|
||||
const nodeConfig = yield select(getNodeConfig);
|
||||
const newWallet = action.payload;
|
||||
|
@ -196,7 +200,11 @@ function* unsetWeb3Node(action): SagaIterator {
|
|||
}
|
||||
|
||||
// switch back to a node with the same network as MetaMask/Mist
|
||||
const equivalentNode = Object.keys(NODES)
|
||||
yield put(changeNodeIntent(equivalentNodeOrDefault(nodeConfig)));
|
||||
}
|
||||
|
||||
export const equivalentNodeOrDefault = nodeConfig => {
|
||||
const node = Object.keys(NODES)
|
||||
.filter(key => key !== 'web3')
|
||||
.reduce((found, key) => {
|
||||
const config = NODES[key];
|
||||
|
@ -210,12 +218,8 @@ function* unsetWeb3Node(action): SagaIterator {
|
|||
}, '');
|
||||
|
||||
// if no equivalent node was found, use the app default
|
||||
const newNode = equivalentNode.length
|
||||
? equivalentNode
|
||||
: configInitialState.nodeSelection;
|
||||
|
||||
yield put(changeNodeIntent(newNode));
|
||||
}
|
||||
return node.length ? node : configInitialState.nodeSelection;
|
||||
};
|
||||
|
||||
export default function* configSaga(): SagaIterator {
|
||||
yield takeLatest(
|
||||
|
|
|
@ -25,7 +25,7 @@ import { getTokens } from 'selectors/wallet';
|
|||
import translate from 'translations';
|
||||
import { TokenValue } from 'libs/units';
|
||||
|
||||
function* getDeterministicWallets(
|
||||
export function* getDeterministicWallets(
|
||||
action: GetDeterministicWalletsAction
|
||||
): SagaIterator {
|
||||
const { seed, dPath, publicKey, chainCode, limit, offset } = action.payload;
|
||||
|
@ -64,7 +64,7 @@ function* getDeterministicWallets(
|
|||
}
|
||||
|
||||
// Grab each wallet's main network token, and update it with it
|
||||
function* updateWalletValues(): SagaIterator {
|
||||
export function* updateWalletValues(): SagaIterator {
|
||||
const node: INode = yield select(getNodeLib);
|
||||
const wallets: DeterministicWalletData[] = yield select(getWallets);
|
||||
|
||||
|
@ -87,7 +87,7 @@ function* updateWalletValues(): SagaIterator {
|
|||
}
|
||||
|
||||
// Grab the current desired token, and update the wallet with it
|
||||
function* updateWalletTokenValues(): SagaIterator {
|
||||
export function* updateWalletTokenValues(): SagaIterator {
|
||||
const desiredToken: string = yield select(getDesiredToken);
|
||||
if (!desiredToken) {
|
||||
return;
|
||||
|
|
|
@ -5,7 +5,9 @@ import {
|
|||
import { delay, SagaIterator } from 'redux-saga';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
function* handleNotification(action: ShowNotificationAction): SagaIterator {
|
||||
export function* handleNotification(
|
||||
action: ShowNotificationAction
|
||||
): SagaIterator {
|
||||
const { duration } = action.payload;
|
||||
// show forever
|
||||
if (duration === 0 || duration === Infinity) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
export const getSwap = (state: AppState): SwapState => state.swap;
|
||||
const ONE_SECOND = 1000;
|
||||
const TEN_SECONDS = ONE_SECOND * 10;
|
||||
const BITY_TIMEOUT_MESSAGE = `
|
||||
export const BITY_TIMEOUT_MESSAGE = `
|
||||
Time has run out.
|
||||
If you have already sent, please wait 1 hour.
|
||||
If your order has not be processed after 1 hour,
|
||||
|
@ -81,7 +81,7 @@ export function* pollBityOrderStatusSaga(): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
function* postBityOrderCreate(
|
||||
export function* postBityOrderCreate(
|
||||
action: BityOrderCreateRequestedSwapAction
|
||||
): SagaIterator {
|
||||
const payload = action.payload;
|
||||
|
|
|
@ -20,7 +20,7 @@ export function* loadBityRates(): SagaIterator {
|
|||
}
|
||||
|
||||
// Fork our recurring API call, watch for the need to cancel.
|
||||
function* handleBityRates(): SagaIterator {
|
||||
export function* handleBityRates(): SagaIterator {
|
||||
const loadBityRatesTask = yield fork(loadBityRates);
|
||||
yield take(TypeKeys.SWAP_STOP_LOAD_BITY_RATES);
|
||||
yield cancel(loadBityRatesTask);
|
||||
|
|
|
@ -39,7 +39,7 @@ import { getNetworkConfig, getNodeLib } from 'selectors/config';
|
|||
import { getTokens, getWalletInst } from 'selectors/wallet';
|
||||
import translate from 'translations';
|
||||
|
||||
function* updateAccountBalance(): SagaIterator {
|
||||
export function* updateAccountBalance(): SagaIterator {
|
||||
try {
|
||||
yield put(setBalancePending());
|
||||
const wallet: null | IWallet = yield select(getWalletInst);
|
||||
|
@ -56,7 +56,7 @@ function* updateAccountBalance(): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
function* updateTokenBalances(): SagaIterator {
|
||||
export function* updateTokenBalances(): SagaIterator {
|
||||
try {
|
||||
const node: INode = yield select(getNodeLib);
|
||||
const wallet: null | IWallet = yield select(getWalletInst);
|
||||
|
@ -87,7 +87,7 @@ function* updateTokenBalances(): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
function* updateBalances(): SagaIterator {
|
||||
export function* updateBalances(): SagaIterator {
|
||||
yield fork(updateAccountBalance);
|
||||
yield fork(updateTokenBalances);
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ export function* unlockKeystore(action: UnlockKeystoreAction): SagaIterator {
|
|||
yield put(setWallet(wallet));
|
||||
}
|
||||
|
||||
function* unlockMnemonic(action: UnlockMnemonicAction): SagaIterator {
|
||||
export function* unlockMnemonic(action: UnlockMnemonicAction): SagaIterator {
|
||||
let wallet;
|
||||
const { phrase, pass, path, address } = action.payload;
|
||||
|
||||
|
@ -139,7 +139,7 @@ function* unlockMnemonic(action: UnlockMnemonicAction): SagaIterator {
|
|||
|
||||
// inspired by v3:
|
||||
// https://github.com/kvhnuke/etherwallet/blob/417115b0ab4dd2033d9108a1a5c00652d38db68d/app/scripts/controllers/decryptWalletCtrl.js#L311
|
||||
function* unlockWeb3(): SagaIterator {
|
||||
export function* unlockWeb3(): SagaIterator {
|
||||
const failMsg1 = 'Could not connect to MetaMask / Mist.';
|
||||
const failMsg2 = 'No accounts found in MetaMask / Mist.';
|
||||
const { web3 } = window as any;
|
||||
|
@ -170,7 +170,7 @@ function* unlockWeb3(): SagaIterator {
|
|||
}
|
||||
}
|
||||
|
||||
function* broadcastTx(action: BroadcastTxRequestedAction): SagaIterator {
|
||||
export function* broadcastTx(action: BroadcastTxRequestedAction): SagaIterator {
|
||||
const signedTx = action.payload.signedTx;
|
||||
try {
|
||||
const node: INode = yield select(getNodeLib);
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`handleNodeChangeIntent* should race getCurrentBlock and delay 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"RACE": Object {
|
||||
"lb": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
"to": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [
|
||||
5000,
|
||||
],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`handleNodeChangeIntent* should select getCustomNodeConfig and match race snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"RACE": Object {
|
||||
"lb": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
"to": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [
|
||||
5000,
|
||||
],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`pollOfflineStatus* should put showNotification and put toggleOfflineConfig if !pingSucceeded && !isOffline 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Object {
|
||||
"duration": Infinity,
|
||||
"id": 0.001,
|
||||
"level": "danger",
|
||||
"msg": "You’ve lost your connection to the network, check your internet
|
||||
connection or try changing networks from the dropdown at the
|
||||
top right of the page.",
|
||||
},
|
||||
"type": "SHOW_NOTIFICATION",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`pollOfflineStatus* should race pingSucceeded and timeout 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"RACE": Object {
|
||||
"pingSucceeded": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
"timeout": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [
|
||||
5000,
|
||||
],
|
||||
"context": null,
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,216 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getDeterministicWallets* starting from publicKey & chainCode should match put snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Array [
|
||||
Object {
|
||||
"address": "0xa3fA2e024bf9964bb64bDf0afdCa8E3c374a6e41",
|
||||
"index": 0,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x9bbC617740413CDCc41fB69901b252e20fd6Ef61",
|
||||
"index": 1,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xE5297818aEee60385306b7087B024af20524C0FD",
|
||||
"index": 2,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x111789ac11B69fE7EbC307c908Efe7677fd347A2",
|
||||
"index": 3,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x315857914bEd907e0Cf33b5883e599dD6ACc45d2",
|
||||
"index": 4,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xEA316E5BFd9FDeCD81489929ae56DBE6ffaDD22C",
|
||||
"index": 5,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x5De67797cEeCeD707A6868bbE96331ED6f811F06",
|
||||
"index": 6,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xd0cb2Fe67f66DF4D6610E99dDE2ad1B0B5bf4054",
|
||||
"index": 7,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xC7D3A4101bf3Ac1A5aE898784F80D2F9A1E67B62",
|
||||
"index": 8,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x12228Ec03f6aFd6Dad66ceD9C8B9762b3080e319",
|
||||
"index": 9,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
],
|
||||
"type": "DW_SET_WALLETS",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getDeterministicWallets* starting from seed should match put snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Array [
|
||||
Object {
|
||||
"address": "0x2e516E79F439469AA3DD43a93429ee45cBeb77Aa",
|
||||
"index": 0,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x4aE493688184D612aF70C0B04aA7f2C8eE03a1dE",
|
||||
"index": 1,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xbd208C37C747f784EFfA379aA417d867216Ace19",
|
||||
"index": 2,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0x85FF91929287F6cdC801280EAf8fa899e22a02F6",
|
||||
"index": 3,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
Object {
|
||||
"address": "0xDF5acC09CDf1f966CEc6eFAB3523804E1de65c7f",
|
||||
"index": 4,
|
||||
"tokenValues": Object {},
|
||||
},
|
||||
],
|
||||
"type": "DW_SET_WALLETS",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`updateWalletTokenValues* should match snapshot for put wallet1 update 1`] = `
|
||||
Object {
|
||||
"done": false,
|
||||
"value": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Object {
|
||||
"address": "0x0",
|
||||
"index": 0,
|
||||
"tokenValues": Object {
|
||||
"OMG": Object {
|
||||
"decimal": 16,
|
||||
"value": "64",
|
||||
},
|
||||
},
|
||||
"value": "64",
|
||||
},
|
||||
"type": "DW_UPDATE_WALLET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`updateWalletTokenValues* should match snapshot for put wallet2 update 1`] = `
|
||||
Object {
|
||||
"done": false,
|
||||
"value": Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Object {
|
||||
"address": "0x1",
|
||||
"index": 1,
|
||||
"tokenValues": Object {
|
||||
"BAT": Object {
|
||||
"decimal": 16,
|
||||
"value": "64",
|
||||
},
|
||||
"OMG": Object {
|
||||
"decimal": 16,
|
||||
"value": "c8",
|
||||
},
|
||||
},
|
||||
"value": "64",
|
||||
},
|
||||
"type": "DW_UPDATE_WALLET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`updateWalletTokenValues* should match snapshot of wallet token balances 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"ALL": Array [
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [
|
||||
"0x0",
|
||||
Object {
|
||||
"address": "0x2",
|
||||
"decimal": 16,
|
||||
"symbol": "OMG",
|
||||
},
|
||||
],
|
||||
"context": RpcNode {
|
||||
"client": RPCClient {
|
||||
"batch": [Function],
|
||||
"call": [Function],
|
||||
"decorateRequest": [Function],
|
||||
"endpoint": "",
|
||||
"headers": Object {},
|
||||
},
|
||||
"requests": RPCRequests {},
|
||||
},
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"CALL": Object {
|
||||
"args": Array [
|
||||
"0x1",
|
||||
Object {
|
||||
"address": "0x2",
|
||||
"decimal": 16,
|
||||
"symbol": "OMG",
|
||||
},
|
||||
],
|
||||
"context": RpcNode {
|
||||
"client": RPCClient {
|
||||
"batch": [Function],
|
||||
"call": [Function],
|
||||
"decorateRequest": [Function],
|
||||
"endpoint": "",
|
||||
"headers": Object {},
|
||||
},
|
||||
"requests": RPCRequests {},
|
||||
},
|
||||
"fn": [Function],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,228 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`broadcastTx* should match put showNotifiction snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Object {
|
||||
"duration": 0,
|
||||
"id": 0.001,
|
||||
"level": "success",
|
||||
"msg": <TransactionSucceeded
|
||||
blockExplorer="foo"
|
||||
txHash="txHash"
|
||||
/>,
|
||||
},
|
||||
"type": "SHOW_NOTIFICATION",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`unlockKeystore* should match put setWallet snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Wallet {
|
||||
"_privKey": Object {
|
||||
"data": Array [
|
||||
139,
|
||||
203,
|
||||
68,
|
||||
86,
|
||||
239,
|
||||
3,
|
||||
86,
|
||||
206,
|
||||
6,
|
||||
44,
|
||||
133,
|
||||
124,
|
||||
239,
|
||||
221,
|
||||
62,
|
||||
209,
|
||||
186,
|
||||
180,
|
||||
84,
|
||||
50,
|
||||
207,
|
||||
118,
|
||||
214,
|
||||
213,
|
||||
52,
|
||||
8,
|
||||
153,
|
||||
207,
|
||||
208,
|
||||
247,
|
||||
2,
|
||||
232,
|
||||
],
|
||||
"type": "Buffer",
|
||||
},
|
||||
"_pubKey": undefined,
|
||||
"signMessage": [Function],
|
||||
"signRawTransaction": [Function],
|
||||
"unlock": [Function],
|
||||
},
|
||||
"type": "WALLET_SET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`unlockMnemonic* should match put setWallet snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Wallet {
|
||||
"_privKey": Object {
|
||||
"data": Array [
|
||||
49,
|
||||
233,
|
||||
127,
|
||||
57,
|
||||
92,
|
||||
171,
|
||||
198,
|
||||
250,
|
||||
163,
|
||||
125,
|
||||
138,
|
||||
157,
|
||||
107,
|
||||
177,
|
||||
133,
|
||||
24,
|
||||
124,
|
||||
53,
|
||||
112,
|
||||
78,
|
||||
123,
|
||||
151,
|
||||
108,
|
||||
122,
|
||||
17,
|
||||
14,
|
||||
47,
|
||||
14,
|
||||
171,
|
||||
55,
|
||||
195,
|
||||
68,
|
||||
],
|
||||
"type": "Buffer",
|
||||
},
|
||||
"_pubKey": undefined,
|
||||
"signMessage": [Function],
|
||||
"signRawTransaction": [Function],
|
||||
"unlock": [Function],
|
||||
},
|
||||
"type": "WALLET_SET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`unlockPrivateKey should match put setWallet snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Wallet {
|
||||
"_privKey": Object {
|
||||
"data": Array [
|
||||
49,
|
||||
233,
|
||||
127,
|
||||
57,
|
||||
92,
|
||||
171,
|
||||
198,
|
||||
250,
|
||||
163,
|
||||
125,
|
||||
138,
|
||||
157,
|
||||
107,
|
||||
177,
|
||||
133,
|
||||
24,
|
||||
124,
|
||||
53,
|
||||
112,
|
||||
78,
|
||||
123,
|
||||
151,
|
||||
108,
|
||||
122,
|
||||
17,
|
||||
14,
|
||||
47,
|
||||
14,
|
||||
171,
|
||||
55,
|
||||
195,
|
||||
68,
|
||||
],
|
||||
"type": "Buffer",
|
||||
},
|
||||
"_pubKey": undefined,
|
||||
"signMessage": [Function],
|
||||
"signRawTransaction": [Function],
|
||||
"unlock": [Function],
|
||||
},
|
||||
"type": "WALLET_SET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`unlockWeb3* should match setWallet snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Web3Wallet {
|
||||
"address": "0xe2EdC95134bbD88443bc6D55b809F7d0C2f0C854",
|
||||
"network": "ETH",
|
||||
"web3": Object {
|
||||
"eth": Object {
|
||||
"getAccounts": [Function],
|
||||
},
|
||||
"network": "1",
|
||||
"version": Object {
|
||||
"getNetwork": [Function],
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": "WALLET_SET",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`updateTokenBalances* should match put setTokenBalances snapshot 1`] = `
|
||||
Object {
|
||||
"@@redux-saga/IO": true,
|
||||
"PUT": Object {
|
||||
"action": Object {
|
||||
"payload": Object {
|
||||
"BAT": "c8",
|
||||
"OMG": "64",
|
||||
},
|
||||
"type": "WALLET_SET_TOKEN_BALANCES",
|
||||
},
|
||||
"channel": null,
|
||||
},
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,417 @@
|
|||
import { configuredStore } from 'store';
|
||||
import { delay } from 'redux-saga';
|
||||
import { call, cancel, fork, put, take, select } from 'redux-saga/effects';
|
||||
import { cloneableGenerator, createMockTask } from 'redux-saga/utils';
|
||||
import {
|
||||
toggleOfflineConfig,
|
||||
changeNode,
|
||||
changeNodeIntent,
|
||||
setLatestBlock
|
||||
} from 'actions/config';
|
||||
import {
|
||||
pollOfflineStatus,
|
||||
handlePollOfflineStatus,
|
||||
handleNodeChangeIntent,
|
||||
handleTogglePollOfflineStatus,
|
||||
reload,
|
||||
unsetWeb3Node,
|
||||
equivalentNodeOrDefault
|
||||
} from 'sagas/config';
|
||||
import { NODES } from 'config/data';
|
||||
import {
|
||||
getNode,
|
||||
getNodeConfig,
|
||||
getOffline,
|
||||
getForceOffline,
|
||||
getCustomNodeConfigs
|
||||
} from 'selectors/config';
|
||||
import { INITIAL_STATE as configInitialState } from 'reducers/config';
|
||||
import { getWalletInst } from 'selectors/wallet';
|
||||
import { Web3Wallet } from 'libs/wallet';
|
||||
import { RPCNode } from 'libs/nodes';
|
||||
import { showNotification } from 'actions/notifications';
|
||||
import translate from 'translations';
|
||||
|
||||
// init module
|
||||
configuredStore.getState();
|
||||
|
||||
describe('pollOfflineStatus*', () => {
|
||||
const nav = navigator as any;
|
||||
const doc = document as any;
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(pollOfflineStatus)();
|
||||
const node = {
|
||||
lib: {
|
||||
ping: jest.fn()
|
||||
}
|
||||
};
|
||||
const isOffline = true;
|
||||
const isForcedOffline = true;
|
||||
const raceSuccess = {
|
||||
pingSucceeded: true
|
||||
};
|
||||
const raceFailure = {
|
||||
pingSucceeded: false
|
||||
};
|
||||
|
||||
let originalHidden;
|
||||
let originalOnLine;
|
||||
let originalRandom;
|
||||
|
||||
beforeAll(() => {
|
||||
// backup global config
|
||||
originalHidden = document.hidden;
|
||||
originalOnLine = navigator.onLine;
|
||||
originalRandom = Math.random;
|
||||
|
||||
// mock config
|
||||
Object.defineProperty(document, 'hidden', { value: false, writable: true });
|
||||
Object.defineProperty(navigator, 'onLine', { value: true, writable: true });
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// restore global config
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
value: originalHidden,
|
||||
writable: false
|
||||
});
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
value: originalOnLine,
|
||||
writable: false
|
||||
});
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should select getNodeConfig', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getNodeConfig));
|
||||
});
|
||||
|
||||
it('should select getOffline', () => {
|
||||
expect(data.gen.next(node).value).toEqual(select(getOffline));
|
||||
});
|
||||
|
||||
it('should select getForceOffline', () => {
|
||||
data.isOfflineClone = data.gen.clone();
|
||||
expect(data.gen.next(isOffline).value).toEqual(select(getForceOffline));
|
||||
});
|
||||
|
||||
it('should be done if isForcedOffline', () => {
|
||||
data.clone1 = data.gen.clone();
|
||||
expect(data.clone1.next(isForcedOffline).done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should call delay if document is hidden', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
doc.hidden = true;
|
||||
|
||||
expect(data.clone2.next(!isForcedOffline).value).toEqual(call(delay, 1000));
|
||||
});
|
||||
|
||||
it('should race pingSucceeded and timeout', () => {
|
||||
doc.hidden = false;
|
||||
expect(data.gen.next(!isForcedOffline).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should put showNotification and put toggleOfflineConfig if pingSucceeded && isOffline', () => {
|
||||
expect(data.gen.next(raceSuccess).value).toEqual(
|
||||
put(
|
||||
showNotification(
|
||||
'success',
|
||||
'Your connection to the network has been restored!',
|
||||
3000
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(data.gen.next().value).toEqual(put(toggleOfflineConfig()));
|
||||
});
|
||||
|
||||
it('should put showNotification and put toggleOfflineConfig if !pingSucceeded && !isOffline', () => {
|
||||
nav.onLine = !isOffline;
|
||||
|
||||
data.isOfflineClone.next(!isOffline);
|
||||
data.isOfflineClone.next(!isForcedOffline);
|
||||
|
||||
data.clone3 = data.isOfflineClone.clone();
|
||||
|
||||
expect(data.isOfflineClone.next(raceFailure).value).toMatchSnapshot();
|
||||
expect(data.isOfflineClone.next().value).toEqual(
|
||||
put(toggleOfflineConfig())
|
||||
);
|
||||
});
|
||||
|
||||
it('should call delay when neither case is true', () => {
|
||||
expect(data.clone3.next(raceSuccess).value).toEqual(call(delay, 5000));
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePollOfflineStatus*', () => {
|
||||
const gen = handlePollOfflineStatus();
|
||||
const mockTask = createMockTask();
|
||||
|
||||
it('should fork pollOffineStatus', () => {
|
||||
const expectedForkYield = fork(pollOfflineStatus);
|
||||
expect(gen.next().value).toEqual(expectedForkYield);
|
||||
});
|
||||
|
||||
it('should take CONFIG_STOP_POLL_OFFLINE_STATE', () => {
|
||||
expect(gen.next(mockTask).value).toEqual(
|
||||
take('CONFIG_STOP_POLL_OFFLINE_STATE')
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel pollOfflineStatus', () => {
|
||||
expect(gen.next().value).toEqual(cancel(mockTask));
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleTogglePollOfflineStatus*', () => {
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(handleTogglePollOfflineStatus)();
|
||||
const isForcedOffline = true;
|
||||
|
||||
it('should select getForceOffline', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getForceOffline));
|
||||
});
|
||||
|
||||
it('should fork handlePollOfflineStatus when isForcedOffline', () => {
|
||||
data.clone = data.gen.clone();
|
||||
expect(data.gen.next(isForcedOffline).value).toEqual(
|
||||
fork(handlePollOfflineStatus)
|
||||
);
|
||||
});
|
||||
|
||||
it('should call handlePollOfflineStatus when !isForcedOffline', () => {
|
||||
expect(data.clone.next(!isForcedOffline).value).toEqual(
|
||||
call(handlePollOfflineStatus)
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(data.gen.next().done).toEqual(true);
|
||||
expect(data.clone.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleNodeChangeIntent*', () => {
|
||||
let originalRandom;
|
||||
|
||||
// normal operation variables
|
||||
const defaultNode = configInitialState.nodeSelection;
|
||||
const defaultNodeConfig = NODES[defaultNode];
|
||||
const newNode = Object.keys(NODES).reduce(
|
||||
(acc, cur) => (NODES[acc].network === defaultNodeConfig.network ? cur : acc)
|
||||
);
|
||||
const newNodeConfig = NODES[newNode];
|
||||
const changeNodeIntentAction = changeNodeIntent(newNode);
|
||||
const truthyWallet = true;
|
||||
const latestBlock = '0xa';
|
||||
const raceSuccess = {
|
||||
lb: latestBlock
|
||||
};
|
||||
const raceFailure = {
|
||||
to: true
|
||||
};
|
||||
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(handleNodeChangeIntent)(changeNodeIntentAction);
|
||||
|
||||
beforeAll(() => {
|
||||
originalRandom = Math.random;
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should select getNode', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getNode));
|
||||
});
|
||||
|
||||
it('should select nodeConfig', () => {
|
||||
expect(data.gen.next(defaultNode).value).toEqual(select(getNodeConfig));
|
||||
});
|
||||
|
||||
it('should race getCurrentBlock and delay', () => {
|
||||
expect(data.gen.next(defaultNodeConfig).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should put showNotification and put changeNode if timeout', () => {
|
||||
data.clone1 = data.gen.clone();
|
||||
expect(data.clone1.next(raceFailure).value).toEqual(
|
||||
put(showNotification('danger', translate('ERROR_32'), 5000))
|
||||
);
|
||||
expect(data.clone1.next().value).toEqual(
|
||||
put(changeNode(defaultNode, defaultNodeConfig))
|
||||
);
|
||||
expect(data.clone1.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should put setLatestBlock', () => {
|
||||
expect(data.gen.next(raceSuccess).value).toEqual(
|
||||
put(setLatestBlock(latestBlock))
|
||||
);
|
||||
});
|
||||
|
||||
it('should put changeNode', () => {
|
||||
expect(data.gen.next().value).toEqual(
|
||||
put(changeNode(changeNodeIntentAction.payload, newNodeConfig))
|
||||
);
|
||||
});
|
||||
|
||||
it('should select getWalletInst', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getWalletInst));
|
||||
});
|
||||
|
||||
it('should call reload if wallet exists and network is new', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
expect(data.clone2.next(truthyWallet).value).toEqual(call(reload));
|
||||
expect(data.clone2.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(data.gen.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
// custom node variables
|
||||
const customNodeConfigs = [
|
||||
{
|
||||
name: 'name',
|
||||
url: 'url',
|
||||
port: 443,
|
||||
network: 'network'
|
||||
}
|
||||
];
|
||||
const customNodeIdFound = 'url:443';
|
||||
const customNodeIdNotFound = 'notFound';
|
||||
const customNodeAction = changeNodeIntent(customNodeIdFound);
|
||||
const customNodeNotFoundAction = changeNodeIntent(customNodeIdNotFound);
|
||||
data.customNode = handleNodeChangeIntent(customNodeAction);
|
||||
data.customNodeNotFound = handleNodeChangeIntent(customNodeNotFoundAction);
|
||||
|
||||
// test custom node
|
||||
it('should select getCustomNodeConfig and match race snapshot', () => {
|
||||
data.customNode.next();
|
||||
data.customNode.next(defaultNode);
|
||||
expect(data.customNode.next(defaultNodeConfig).value).toEqual(
|
||||
select(getCustomNodeConfigs)
|
||||
);
|
||||
expect(data.customNode.next(customNodeConfigs).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// test custom node not found
|
||||
it('should select getCustomNodeConfig, put showNotification, put changeNode', () => {
|
||||
data.customNodeNotFound.next();
|
||||
data.customNodeNotFound.next(defaultNode);
|
||||
expect(data.customNodeNotFound.next(defaultNodeConfig).value).toEqual(
|
||||
select(getCustomNodeConfigs)
|
||||
);
|
||||
expect(data.customNodeNotFound.next(customNodeConfigs).value).toEqual(
|
||||
put(
|
||||
showNotification(
|
||||
'danger',
|
||||
`Attempted to switch to unknown node '${
|
||||
customNodeNotFoundAction.payload
|
||||
}'`,
|
||||
5000
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(data.customNodeNotFound.next().value).toEqual(
|
||||
put(changeNode(defaultNode, defaultNodeConfig))
|
||||
);
|
||||
expect(data.customNodeNotFound.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsetWeb3Node*', () => {
|
||||
const fakeAction = {};
|
||||
const mockNode = 'web3';
|
||||
const mockNodeConfig = { network: 'ETH' };
|
||||
const gen = unsetWeb3Node(fakeAction);
|
||||
|
||||
it('should select getNode', () => {
|
||||
expect(gen.next().value).toEqual(select(getNode));
|
||||
});
|
||||
|
||||
it('should select getNodeConfig', () => {
|
||||
expect(gen.next(mockNode).value).toEqual(select(getNodeConfig));
|
||||
});
|
||||
|
||||
it('should put changeNodeIntent', () => {
|
||||
expect(gen.next(mockNodeConfig).value).toEqual(
|
||||
put(changeNodeIntent(equivalentNodeOrDefault(mockNodeConfig)))
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return early if node type is not web3', () => {
|
||||
const gen1 = unsetWeb3Node({ payload: false });
|
||||
gen1.next(); //getNode
|
||||
gen1.next('notWeb3'); //getNodeConfig
|
||||
expect(gen1.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return early if wallet type is web3', () => {
|
||||
const mockWeb3 = {};
|
||||
const mockAddress = '0x0';
|
||||
const mockNetwork = 'ETH';
|
||||
const mockWeb3Wallet = new Web3Wallet(mockWeb3, mockAddress, mockNetwork);
|
||||
const gen2 = unsetWeb3Node({ payload: mockWeb3Wallet });
|
||||
gen2.next(); //getNode
|
||||
gen2.next('web3'); //getNodeConfig
|
||||
expect(gen2.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('equivalentNodeOrDefault', () => {
|
||||
const originalNodeList = Object.keys(NODES);
|
||||
const appDefaultNode = configInitialState.nodeSelection;
|
||||
const mockNodeConfig = {
|
||||
network: 'ETH',
|
||||
service: 'fakeService',
|
||||
lib: new RPCNode('fakeEndpoint'),
|
||||
estimateGas: false
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
Object.keys(NODES).forEach(node => {
|
||||
if (originalNodeList.indexOf(node) === -1) {
|
||||
delete NODES[node];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return node with equivalent network', () => {
|
||||
const node = equivalentNodeOrDefault({
|
||||
...mockNodeConfig,
|
||||
network: 'Kovan'
|
||||
});
|
||||
expect(NODES[node].network).toEqual('Kovan');
|
||||
});
|
||||
|
||||
it('should return app default if no eqivalent is found', () => {
|
||||
const node = equivalentNodeOrDefault({
|
||||
...mockNodeConfig,
|
||||
network: 'noEqivalentExists'
|
||||
});
|
||||
expect(node).toEqual(appDefaultNode);
|
||||
});
|
||||
|
||||
it('should ignore web3 from node list', () => {
|
||||
NODES.web3 = {
|
||||
...mockNodeConfig,
|
||||
network: 'uniqueToWeb3'
|
||||
};
|
||||
|
||||
const node = equivalentNodeOrDefault({
|
||||
...mockNodeConfig,
|
||||
network: 'uniqueToWeb3'
|
||||
});
|
||||
expect(node).toEqual(appDefaultNode);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,220 @@
|
|||
import { configuredStore } from 'store';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { cloneableGenerator } from 'redux-saga/utils';
|
||||
import { all, apply, fork, put, select } from 'redux-saga/effects';
|
||||
import RpcNode from 'libs/nodes/rpc';
|
||||
import { getDesiredToken, getWallets } from 'selectors/deterministicWallets';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import { getNodeLib } from 'selectors/config';
|
||||
import * as dWalletActions from 'actions/deterministicWallets';
|
||||
import { Token } from 'config/data';
|
||||
import {
|
||||
getDeterministicWallets,
|
||||
updateWalletValues,
|
||||
updateWalletTokenValues
|
||||
} from 'sagas/deterministicWallets';
|
||||
import { TokenValue, Wei } from 'libs/units';
|
||||
|
||||
// init module
|
||||
configuredStore.getState();
|
||||
|
||||
const genWalletData1 = () => ({
|
||||
index: 0,
|
||||
address: '0x0',
|
||||
value: TokenValue('100'),
|
||||
tokenValues: {
|
||||
OMG: {
|
||||
value: TokenValue('100'),
|
||||
decimal: 16
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const genWalletData2 = () => ({
|
||||
index: 1,
|
||||
address: '0x1',
|
||||
value: TokenValue('100'),
|
||||
tokenValues: {
|
||||
BAT: {
|
||||
value: TokenValue('100'),
|
||||
decimal: 16
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const genBalances = () => [Wei('100'), Wei('200')];
|
||||
|
||||
describe('getDeterministicWallets*', () => {
|
||||
describe('starting from seed', () => {
|
||||
const dWallet = {
|
||||
seed:
|
||||
'1ba4b713b9cf6f91e8e2eea015fc4e107452fa7d8ade32322207967371e5c0fb93289d4dde94ce13625ecc60279d211b6d677c67f54b9e97c7e68afc9ca1b5ea',
|
||||
dPath: "m/44'/60'/0'/0"
|
||||
};
|
||||
const action = dWalletActions.getDeterministicWallets(dWallet);
|
||||
const gen = getDeterministicWallets(action);
|
||||
|
||||
it('should match put snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should fork updateWalletValues', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateWalletValues));
|
||||
});
|
||||
|
||||
it('should fork updateWalletTokenValues', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateWalletTokenValues));
|
||||
});
|
||||
});
|
||||
|
||||
describe('starting from publicKey & chainCode', () => {
|
||||
const dWallet = {
|
||||
dPath: '',
|
||||
publicKey:
|
||||
'02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f',
|
||||
chainCode:
|
||||
'180c998615636cd875aa70c71cfa6b7bf570187a56d8c6d054e60b644d13e9d3',
|
||||
limit: 10,
|
||||
offset: 0
|
||||
};
|
||||
|
||||
const action = dWalletActions.getDeterministicWallets(dWallet);
|
||||
const gen = getDeterministicWallets(action);
|
||||
|
||||
it('should match put snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should fork updateWalletValues', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateWalletValues));
|
||||
});
|
||||
|
||||
it('should fork updateWalletTokenValues', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateWalletTokenValues));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateWalletValues*', () => {
|
||||
const walletData1 = genWalletData1();
|
||||
const walletData2 = genWalletData2();
|
||||
const wallets: dWalletActions.DeterministicWalletData[] = [
|
||||
walletData1,
|
||||
walletData2
|
||||
];
|
||||
const balances = genBalances();
|
||||
const node: INode = new RpcNode('');
|
||||
const gen = updateWalletValues();
|
||||
|
||||
it('should select getNodeLib', () => {
|
||||
expect(gen.next().value).toEqual(select(getNodeLib));
|
||||
});
|
||||
|
||||
it('should select getWallets', () => {
|
||||
expect(gen.next(node).value).toEqual(select(getWallets));
|
||||
});
|
||||
|
||||
it('should get balance for all wallets', () => {
|
||||
expect(gen.next(wallets).value).toEqual(
|
||||
all([
|
||||
apply(node, node.getBalance, [walletData1.address]),
|
||||
apply(node, node.getBalance, [walletData2.address])
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should put updateDeterministicWallet for wallet1', () => {
|
||||
expect(gen.next(balances).value).toEqual(
|
||||
put(
|
||||
dWalletActions.updateDeterministicWallet({
|
||||
...walletData1,
|
||||
value: balances[0]
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should put updateDeterministicWallet for wallet2', () => {
|
||||
expect(gen.next(balances).value).toEqual(
|
||||
put(
|
||||
dWalletActions.updateDeterministicWallet({
|
||||
...walletData2,
|
||||
value: balances[1]
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateWalletTokenValues*', () => {
|
||||
const walletData1 = genWalletData1();
|
||||
const walletData2 = genWalletData2();
|
||||
const wallets: dWalletActions.DeterministicWalletData[] = [
|
||||
walletData1,
|
||||
walletData2
|
||||
];
|
||||
const node: INode = new RpcNode('');
|
||||
const token1: Token = {
|
||||
address: '0x2',
|
||||
symbol: 'OMG',
|
||||
decimal: 16
|
||||
};
|
||||
const token2: Token = {
|
||||
address: '0x3',
|
||||
symbol: 'BAT',
|
||||
decimal: 16
|
||||
};
|
||||
const tokens = [token1, token2];
|
||||
const tokenBalances = [TokenValue('100'), TokenValue('200')];
|
||||
const desiredToken = 'OMG';
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(updateWalletTokenValues)();
|
||||
|
||||
it('should select getDesiredToken', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getDesiredToken));
|
||||
});
|
||||
|
||||
it('should return if desired token is falsey', () => {
|
||||
data.clone1 = data.gen.clone();
|
||||
data.clone1.next();
|
||||
expect(data.clone1.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should select getTokens', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
expect(data.gen.next(desiredToken).value).toEqual(select(getTokens));
|
||||
});
|
||||
|
||||
it('should return if desired token is not amongst tokens', () => {
|
||||
data.clone2.next('fakeDesiredToken');
|
||||
expect(data.clone2.next(tokens).done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should select getNodeLib', () => {
|
||||
expect(data.gen.next(tokens).value).toEqual(select(getNodeLib));
|
||||
});
|
||||
|
||||
it('should select getWallets', () => {
|
||||
expect(data.gen.next(node).value).toEqual(select(getWallets));
|
||||
});
|
||||
|
||||
it('should match snapshot of wallet token balances', () => {
|
||||
expect(data.gen.next(wallets).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot for put wallet1 update', () => {
|
||||
expect(data.gen.next(tokenBalances)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match snapshot for put wallet2 update', () => {
|
||||
expect(data.gen.next()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(data.gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
import { delay } from 'redux-saga';
|
||||
import { call, put } from 'redux-saga/effects';
|
||||
import { handleNotification } from 'sagas/notifications';
|
||||
import {
|
||||
ShowNotificationAction,
|
||||
showNotification,
|
||||
closeNotification
|
||||
} from 'actions/notifications';
|
||||
|
||||
describe('handleNotification*', () => {
|
||||
const level = 'success';
|
||||
const msg = 'msg';
|
||||
const duration = 10;
|
||||
const notificationAction1: ShowNotificationAction = showNotification(
|
||||
level,
|
||||
msg,
|
||||
duration
|
||||
);
|
||||
const notificationAction2: ShowNotificationAction = showNotification(
|
||||
level,
|
||||
msg,
|
||||
0
|
||||
);
|
||||
const gen1 = handleNotification(notificationAction1);
|
||||
const gen2 = handleNotification(notificationAction2);
|
||||
|
||||
it('should call delay with duration', () => {
|
||||
expect(gen1.next(notificationAction1).value).toEqual(call(delay, duration));
|
||||
});
|
||||
|
||||
it('should return when duration is zero', () => {
|
||||
expect(gen2.next(notificationAction2).done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should put closeNotification', () => {
|
||||
expect(gen1.next(notificationAction1).value).toEqual(
|
||||
put(closeNotification(notificationAction1.payload))
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen1.next().done).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,371 @@
|
|||
import { showNotification } from 'actions/notifications';
|
||||
import {
|
||||
bityOrderCreateFailedSwap,
|
||||
bityOrderCreateSucceededSwap,
|
||||
bityOrderCreateRequestedSwap,
|
||||
BityOrderPostResponse,
|
||||
BityOrderInput,
|
||||
BityOrderOutput,
|
||||
BityOrderResponse,
|
||||
changeStepSwap,
|
||||
orderStatusRequestedSwap,
|
||||
orderStatusSucceededSwap,
|
||||
orderTimeSwap,
|
||||
startOrderTimerSwap,
|
||||
startPollBityOrderStatus,
|
||||
stopLoadBityRatesSwap,
|
||||
stopPollBityOrderStatus
|
||||
} from 'actions/swap';
|
||||
import { getOrderStatus, postOrder } from 'api/bity';
|
||||
import {
|
||||
State as SwapState,
|
||||
INITIAL_STATE as INITIAL_SWAP_STATE
|
||||
} from 'reducers/swap';
|
||||
import { delay } from 'redux-saga';
|
||||
import {
|
||||
call,
|
||||
cancel,
|
||||
cancelled,
|
||||
fork,
|
||||
put,
|
||||
select,
|
||||
take,
|
||||
takeEvery
|
||||
} from 'redux-saga/effects';
|
||||
import {
|
||||
getSwap,
|
||||
pollBityOrderStatus,
|
||||
pollBityOrderStatusSaga,
|
||||
postBityOrderCreate,
|
||||
postBityOrderSaga,
|
||||
bityTimeRemaining,
|
||||
BITY_TIMEOUT_MESSAGE
|
||||
} from 'sagas/swap/orders';
|
||||
import { cloneableGenerator, createMockTask } from 'redux-saga/utils';
|
||||
|
||||
const ONE_SECOND = 1000;
|
||||
const TEN_SECONDS = ONE_SECOND * 10;
|
||||
const ELEVEN_SECONDS = ONE_SECOND * 11;
|
||||
|
||||
const orderInput: BityOrderInput = {
|
||||
amount: 'amount',
|
||||
currency: 'currency',
|
||||
reference: 'reference',
|
||||
status: 'status'
|
||||
};
|
||||
const orderOutput: BityOrderOutput = {
|
||||
amount: 'amount',
|
||||
currency: 'currency',
|
||||
reference: 'reference',
|
||||
status: 'status'
|
||||
};
|
||||
|
||||
describe('pollBityOrderStatus*', () => {
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(pollBityOrderStatus)();
|
||||
const fakeSwap: SwapState = {
|
||||
...INITIAL_SWAP_STATE,
|
||||
orderId: '1'
|
||||
};
|
||||
const orderResponse: BityOrderResponse = {
|
||||
input: orderInput,
|
||||
output: orderOutput,
|
||||
status: 'status'
|
||||
};
|
||||
const cancelledSwap = 'CANC';
|
||||
const successStatus = {
|
||||
error: null,
|
||||
data: orderResponse
|
||||
};
|
||||
const errorStatus = {
|
||||
error: true,
|
||||
msg: 'error message'
|
||||
};
|
||||
let random;
|
||||
|
||||
beforeAll(() => {
|
||||
random = Math.random;
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = random;
|
||||
});
|
||||
|
||||
it('should select getSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getSwap));
|
||||
});
|
||||
|
||||
it('should put orderStatusRequestedSwap', () => {
|
||||
expect(data.gen.next(fakeSwap).value).toEqual(
|
||||
put(orderStatusRequestedSwap())
|
||||
);
|
||||
});
|
||||
|
||||
it('should call getOrderStatus with swap.orderId', () => {
|
||||
expect(data.gen.next().value).toEqual(
|
||||
call(getOrderStatus, fakeSwap.orderId)
|
||||
);
|
||||
});
|
||||
|
||||
it('should put showNotfication on error', () => {
|
||||
data.clone = data.gen.clone();
|
||||
expect(data.clone.next(errorStatus).value).toEqual(
|
||||
put(
|
||||
showNotification(
|
||||
'danger',
|
||||
`Bity Error: ${errorStatus.msg}`,
|
||||
TEN_SECONDS
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should put orderStatusSucceededSwap', () => {
|
||||
expect(data.gen.next(successStatus).value).toEqual(
|
||||
put(orderStatusSucceededSwap(successStatus.data))
|
||||
);
|
||||
});
|
||||
|
||||
it('should call delay for 5 seconds', () => {
|
||||
expect(data.gen.next().value).toEqual(call(delay, ONE_SECOND * 5));
|
||||
});
|
||||
|
||||
it('should select getSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getSwap));
|
||||
});
|
||||
|
||||
it('should break loop if swap is cancelled', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
expect(data.clone2.next(cancelledSwap).value).toEqual(cancelled());
|
||||
expect(data.clone2.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should restart loop', () => {
|
||||
expect(data.gen.next(fakeSwap).value).toEqual(
|
||||
put(orderStatusRequestedSwap())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pollBityOrderStatusSaga*', () => {
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(pollBityOrderStatusSaga)();
|
||||
const mockedTask = createMockTask();
|
||||
|
||||
it('should take SWAP_START_POLL_BITY_ORDER_STATUS', () => {
|
||||
expect(data.gen.next().value).toEqual(
|
||||
take('SWAP_START_POLL_BITY_ORDER_STATUS')
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done if order status is false', () => {
|
||||
data.clone = data.gen.clone();
|
||||
expect(data.clone.next(false).done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should fork pollBityOrderStatus', () => {
|
||||
expect(data.gen.next(true).value).toEqual(fork(pollBityOrderStatus));
|
||||
});
|
||||
|
||||
it('should take SWAP_STOP_POLL_BITY_ORDER_STATUS', () => {
|
||||
expect(data.gen.next(mockedTask).value).toEqual(
|
||||
take('SWAP_STOP_POLL_BITY_ORDER_STATUS')
|
||||
);
|
||||
});
|
||||
|
||||
it('should cancel pollBityOrderStatusTask', () => {
|
||||
expect(data.gen.next().value).toEqual(cancel(mockedTask));
|
||||
});
|
||||
});
|
||||
|
||||
describe('postBityOrderCreate*', () => {
|
||||
const amount = 100;
|
||||
const destinationAddress = '0x0';
|
||||
const pair = 'BTC_ETH';
|
||||
const action = bityOrderCreateRequestedSwap(amount, destinationAddress, pair);
|
||||
const orderResp: BityOrderPostResponse = {
|
||||
payment_address: '0x0',
|
||||
status: 'status',
|
||||
input: orderInput,
|
||||
output: orderOutput,
|
||||
timestamp_created: 'timestamp_created',
|
||||
validFor: 10,
|
||||
id: '0'
|
||||
};
|
||||
const successOrder = { error: false, data: orderResp };
|
||||
const errorOrder = { error: true, msg: 'error msg' };
|
||||
const connectionErrMsg =
|
||||
'Connection Error. Please check the developer console for more details and/or contact support';
|
||||
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(postBityOrderCreate)(action);
|
||||
|
||||
let random;
|
||||
beforeAll(() => {
|
||||
random = Math.random;
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = random;
|
||||
});
|
||||
|
||||
it('should put stopLoadBityRatesSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(put(stopLoadBityRatesSwap()));
|
||||
});
|
||||
|
||||
it('should call postOrder', () => {
|
||||
data.clone1 = data.gen.clone();
|
||||
expect(data.gen.next().value).toEqual(
|
||||
call(postOrder, amount, destinationAddress, action.payload.mode, pair)
|
||||
);
|
||||
});
|
||||
|
||||
it('should put bityOrderCreateSucceededSwap', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
expect(data.gen.next(successOrder).value).toEqual(
|
||||
put(bityOrderCreateSucceededSwap(successOrder.data))
|
||||
);
|
||||
});
|
||||
|
||||
it('should put changeStepSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(put(changeStepSwap(3)));
|
||||
});
|
||||
|
||||
it('should put startOrderTimerSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(put(startOrderTimerSwap()));
|
||||
});
|
||||
|
||||
it('should put startPollBityOrderStatus', () => {
|
||||
expect(data.gen.next().value).toEqual(put(startPollBityOrderStatus()));
|
||||
});
|
||||
|
||||
// failure modes
|
||||
it('should handle a connection exeception', () => {
|
||||
expect(data.clone1.throw().value).toEqual(
|
||||
put(showNotification('danger', connectionErrMsg, TEN_SECONDS))
|
||||
);
|
||||
expect(data.clone1.next().value).toEqual(put(bityOrderCreateFailedSwap()));
|
||||
expect(data.clone1.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle an errored order', () => {
|
||||
expect(data.clone2.next(errorOrder).value).toEqual(
|
||||
put(
|
||||
showNotification('danger', `Bity Error: ${errorOrder.msg}`, TEN_SECONDS)
|
||||
)
|
||||
);
|
||||
expect(data.clone2.next().value).toEqual(put(bityOrderCreateFailedSwap()));
|
||||
});
|
||||
});
|
||||
|
||||
describe('postBityOrderSaga*', () => {
|
||||
const gen = postBityOrderSaga();
|
||||
|
||||
it('should takeEvery SWAP_ORDER_CREATE_REQUESTED', () => {
|
||||
expect(gen.next().value).toEqual(
|
||||
takeEvery('SWAP_ORDER_CREATE_REQUESTED', postBityOrderCreate)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bityTimeRemaining*', () => {
|
||||
const orderTime = new Date().toISOString();
|
||||
const orderTimeExpired = new Date().getTime() - ELEVEN_SECONDS;
|
||||
const swapValidFor = 10; //seconds
|
||||
const swapOrder = {
|
||||
...INITIAL_SWAP_STATE,
|
||||
orderTimestampCreatedISOString: orderTime,
|
||||
validFor: swapValidFor
|
||||
};
|
||||
const swapOrderExpired = {
|
||||
...INITIAL_SWAP_STATE,
|
||||
orderTimestampCreatedISOString: new Date(orderTimeExpired).toISOString(),
|
||||
validFor: swapValidFor
|
||||
};
|
||||
let random;
|
||||
|
||||
const data = {} as any;
|
||||
data.gen = cloneableGenerator(bityTimeRemaining)();
|
||||
|
||||
beforeAll(() => {
|
||||
random = Math.random;
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = random;
|
||||
});
|
||||
|
||||
it('should take SWAP_ORDER_START_TIMER', () => {
|
||||
expect(data.gen.next().value).toEqual(take('SWAP_ORDER_START_TIMER'));
|
||||
});
|
||||
|
||||
it('should break while loop when take SWAP_ORDER_START_TIMER is false', () => {
|
||||
data.clone1 = data.gen.clone();
|
||||
expect(data.clone1.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should call delay of one second', () => {
|
||||
expect(data.gen.next(true).value).toEqual(call(delay, ONE_SECOND));
|
||||
});
|
||||
|
||||
it('should select getSwap', () => {
|
||||
expect(data.gen.next().value).toEqual(select(getSwap));
|
||||
});
|
||||
|
||||
it('should handle if isValidUntil.isAfter(now)', () => {
|
||||
data.clone2 = data.gen.clone();
|
||||
const result = data.clone2.next(swapOrder).value;
|
||||
expect(result).toHaveProperty('PUT');
|
||||
expect(result.PUT.action.type).toEqual('SWAP_ORDER_TIME');
|
||||
expect(result.PUT.action.payload).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle an OPEN order state', () => {
|
||||
const openOrder = { ...swapOrderExpired, orderStatus: 'OPEN' };
|
||||
data.OPEN = data.gen.clone();
|
||||
expect(data.OPEN.next(openOrder).value).toEqual(put(orderTimeSwap(0)));
|
||||
expect(data.OPEN.next().value).toEqual(put(stopPollBityOrderStatus()));
|
||||
expect(data.OPEN.next().value).toEqual(
|
||||
put({ type: 'SWAP_STOP_LOAD_BITY_RATES' })
|
||||
);
|
||||
expect(data.OPEN.next().value).toEqual(
|
||||
put(showNotification('danger', BITY_TIMEOUT_MESSAGE, Infinity))
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a CANC order state', () => {
|
||||
const cancOrder = { ...swapOrderExpired, orderStatus: 'CANC' };
|
||||
data.CANC = data.gen.clone();
|
||||
expect(data.CANC.next(cancOrder).value).toEqual(
|
||||
put(stopPollBityOrderStatus())
|
||||
);
|
||||
expect(data.CANC.next().value).toEqual(
|
||||
put({ type: 'SWAP_STOP_LOAD_BITY_RATES' })
|
||||
);
|
||||
expect(data.CANC.next().value).toEqual(
|
||||
put(showNotification('danger', BITY_TIMEOUT_MESSAGE, Infinity))
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a RCVE order state', () => {
|
||||
const rcveOrder = { ...swapOrderExpired, orderStatus: 'RCVE' };
|
||||
data.RCVE = data.gen.clone();
|
||||
expect(data.RCVE.next(rcveOrder).value).toEqual(
|
||||
put(showNotification('warning', BITY_TIMEOUT_MESSAGE, Infinity))
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle a FILL order state', () => {
|
||||
const fillOrder = { ...swapOrderExpired, orderStatus: 'FILL' };
|
||||
data.FILL = data.gen.clone();
|
||||
expect(data.FILL.next(fillOrder).value).toEqual(
|
||||
put(stopPollBityOrderStatus())
|
||||
);
|
||||
expect(data.FILL.next().value).toEqual(
|
||||
put({ type: 'SWAP_STOP_LOAD_BITY_RATES' })
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
import { showNotification } from 'actions/notifications';
|
||||
import { loadBityRatesSucceededSwap } from 'actions/swap';
|
||||
import { getAllRates } from 'api/bity';
|
||||
import { delay } from 'redux-saga';
|
||||
import { call, cancel, fork, put, take, takeLatest } from 'redux-saga/effects';
|
||||
import { createMockTask } from 'redux-saga/utils';
|
||||
import { Pairs } from 'actions/swap/actionTypes';
|
||||
import {
|
||||
loadBityRates,
|
||||
handleBityRates,
|
||||
getBityRatesSaga
|
||||
} from 'sagas/swap/rates';
|
||||
|
||||
describe('loadBityRates*', () => {
|
||||
const gen1 = loadBityRates();
|
||||
const gen2 = loadBityRates();
|
||||
const rates: Pairs = {
|
||||
ETHBTC: 1,
|
||||
ETHREP: 2,
|
||||
BTCETH: 3,
|
||||
BTCREP: 4
|
||||
};
|
||||
let random;
|
||||
|
||||
beforeAll(() => {
|
||||
random = Math.random;
|
||||
Math.random = () => 0.001;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = random;
|
||||
});
|
||||
|
||||
it('should call getAllRates', () => {
|
||||
expect(gen1.next().value).toEqual(call(getAllRates));
|
||||
});
|
||||
|
||||
it('should put loadBityRatesSucceededSwap', () => {
|
||||
expect(gen1.next(rates).value).toEqual(
|
||||
put(loadBityRatesSucceededSwap(rates))
|
||||
);
|
||||
});
|
||||
|
||||
it('should call delay for 5 seconds', () => {
|
||||
expect(gen1.next().value).toEqual(call(delay, 30000));
|
||||
});
|
||||
|
||||
it('should handle an exception', () => {
|
||||
const err = { message: 'error' };
|
||||
gen2.next();
|
||||
expect((gen2 as any).throw(err).value).toEqual(
|
||||
put(showNotification('danger', err.message))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleBityRates*', () => {
|
||||
const gen = handleBityRates();
|
||||
const mockTask = createMockTask();
|
||||
|
||||
it('should fork loadBityRates', () => {
|
||||
expect(gen.next().value).toEqual(fork(loadBityRates));
|
||||
});
|
||||
|
||||
it('should take SWAP_STOP_LOAD_BITY_RATES', () => {
|
||||
expect(gen.next(mockTask).value).toEqual(take('SWAP_STOP_LOAD_BITY_RATES'));
|
||||
});
|
||||
|
||||
it('should cancel loadBityRatesTask', () => {
|
||||
expect(gen.next().value).toEqual(cancel(mockTask));
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBityRatesSaga*', () => {
|
||||
const gen = getBityRatesSaga();
|
||||
|
||||
it('should takeLatest SWAP_LOAD_RATES_REQUESTED', () => {
|
||||
expect(gen.next().value).toEqual(
|
||||
takeLatest('SWAP_LOAD_BITY_RATES_REQUESTED', handleBityRates)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,327 @@
|
|||
import { configuredStore } from 'store';
|
||||
import RpcNode from 'libs/nodes/rpc';
|
||||
import {
|
||||
broadcastTxSucceded,
|
||||
setBalanceFullfilled,
|
||||
setBalancePending,
|
||||
unlockPrivateKey as unlockPrivateKeyActionGen,
|
||||
unlockKeystore as unlockKeystoreActionGen,
|
||||
unlockMnemonic as unlockMnemonicActionGen,
|
||||
broadcastTx as broadcastTxActionGen
|
||||
} from 'actions/wallet';
|
||||
import { Wei } from 'libs/units';
|
||||
import { changeNodeIntent } from 'actions/config';
|
||||
import { INode } from 'libs/nodes/INode';
|
||||
import { initWeb3Node, Token } from 'config/data';
|
||||
import { apply, call, cps, fork, put, select } from 'redux-saga/effects';
|
||||
import { getNetworkConfig, getNodeLib } from 'selectors/config';
|
||||
import { getTokens, getWalletInst } from 'selectors/wallet';
|
||||
import {
|
||||
updateAccountBalance,
|
||||
updateTokenBalances,
|
||||
updateBalances,
|
||||
unlockPrivateKey,
|
||||
unlockKeystore,
|
||||
unlockMnemonic,
|
||||
unlockWeb3,
|
||||
broadcastTx
|
||||
} from 'sagas/wallet';
|
||||
import { PrivKeyWallet } from 'libs/wallet/non-deterministic';
|
||||
|
||||
// init module
|
||||
configuredStore.getState();
|
||||
|
||||
const pkey = '31e97f395cabc6faa37d8a9d6bb185187c35704e7b976c7a110e2f0eab37c344';
|
||||
const wallet = PrivKeyWallet(Buffer.from(pkey, 'hex'));
|
||||
const address = '0xe2EdC95134bbD88443bc6D55b809F7d0C2f0C854';
|
||||
const balance = Wei('100');
|
||||
const node: INode = new RpcNode('');
|
||||
|
||||
const token1: Token = {
|
||||
address: '0x2',
|
||||
symbol: 'OMG',
|
||||
decimal: 16
|
||||
};
|
||||
const token2: Token = {
|
||||
address: '0x3',
|
||||
symbol: 'BAT',
|
||||
decimal: 16
|
||||
};
|
||||
const tokens = [token1, token2];
|
||||
const balances = [Wei('100'), Wei('200')];
|
||||
|
||||
const utcKeystore = {
|
||||
version: 3,
|
||||
id: 'cb788af4-993d-43ad-851b-0d2031e52c61',
|
||||
address: '25a24679f35e447f778cf54a3823facf39904a63',
|
||||
Crypto: {
|
||||
ciphertext:
|
||||
'4193915c560835d00b2b9ff5dd20f3e13793b2a3ca8a97df649286063f27f707',
|
||||
cipherparams: {
|
||||
iv: 'dccb8c009b11d1c6226ba19b557dce4c'
|
||||
},
|
||||
cipher: 'aes-128-ctr',
|
||||
kdf: 'scrypt',
|
||||
kdfparams: {
|
||||
dklen: 32,
|
||||
salt: '037a53e520f2d00fb70f02f39b31b77374de9e0e1d35fd7cbe9c8a8b21d6b0ab',
|
||||
n: 1024,
|
||||
r: 8,
|
||||
p: 1
|
||||
},
|
||||
mac: '774fbe4bf35e7e28df15cd6c3546e74ce6608e9ab68a88d50227858a3b05769a'
|
||||
}
|
||||
};
|
||||
|
||||
// necessary so we can later inject a mocked web3 to the window
|
||||
declare var window: any;
|
||||
|
||||
describe('updateAccountBalance*', () => {
|
||||
const gen1 = updateAccountBalance();
|
||||
const gen2 = updateAccountBalance();
|
||||
|
||||
it('should put setBalancePending', () => {
|
||||
expect(gen1.next().value).toEqual(put(setBalancePending()));
|
||||
});
|
||||
|
||||
it('should select getWalletInst', () => {
|
||||
expect(gen1.next().value).toEqual(select(getWalletInst));
|
||||
});
|
||||
|
||||
it('should return if wallet is falsey', () => {
|
||||
gen2.next();
|
||||
gen2.next();
|
||||
gen2.next(null);
|
||||
expect(gen2.next().done).toBe(true);
|
||||
});
|
||||
|
||||
it('should select getNodeLib', () => {
|
||||
expect(gen1.next(wallet).value).toEqual(select(getNodeLib));
|
||||
});
|
||||
|
||||
it('should apply wallet.getAddressString', () => {
|
||||
expect(gen1.next(node).value).toEqual(
|
||||
apply(wallet, wallet.getAddressString)
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply node.getBalance', () => {
|
||||
expect(gen1.next(address).value).toEqual(
|
||||
apply(node, node.getBalance, [address])
|
||||
);
|
||||
});
|
||||
|
||||
it('should put setBalanceFulfilled', () => {
|
||||
expect(gen1.next(balance).value).toEqual(
|
||||
put(setBalanceFullfilled(balance))
|
||||
);
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen1.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTokenBalances*', () => {
|
||||
const gen1 = updateTokenBalances();
|
||||
const gen2 = updateTokenBalances();
|
||||
const gen3 = updateTokenBalances();
|
||||
|
||||
it('should select getNodeLib', () => {
|
||||
expect(gen1.next().value).toEqual(select(getNodeLib));
|
||||
});
|
||||
|
||||
it('should select getWalletInst', () => {
|
||||
expect(gen1.next(node).value).toEqual(select(getWalletInst));
|
||||
});
|
||||
|
||||
it('should select getTokens', () => {
|
||||
expect(gen1.next(wallet).value).toEqual(select(getTokens));
|
||||
});
|
||||
|
||||
it('should return if wallet is falsey', () => {
|
||||
gen2.next();
|
||||
gen2.next(node);
|
||||
gen2.next(null);
|
||||
expect(gen2.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return if node is falsey', () => {
|
||||
gen3.next();
|
||||
gen3.next(null);
|
||||
gen3.next(wallet);
|
||||
expect(gen3.next().done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should apply wallet.getAddressString', () => {
|
||||
expect(gen1.next(tokens).value).toEqual(
|
||||
apply(wallet, wallet.getAddressString)
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply node.getTokenBalances', () => {
|
||||
expect(gen1.next(address).value).toEqual(
|
||||
apply(node, node.getTokenBalances, [address, tokens])
|
||||
);
|
||||
});
|
||||
|
||||
it('should match put setTokenBalances snapshot', () => {
|
||||
expect(gen1.next(balances).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen1.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateBalances*', () => {
|
||||
const gen = updateBalances();
|
||||
|
||||
it('should fork updateAccountBalance', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateAccountBalance));
|
||||
});
|
||||
|
||||
it('should fork updateTokenBalances', () => {
|
||||
expect(gen.next().value).toEqual(fork(updateTokenBalances));
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockPrivateKey', () => {
|
||||
const value = {
|
||||
key: pkey,
|
||||
password: ''
|
||||
};
|
||||
const action = unlockPrivateKeyActionGen(value);
|
||||
const gen = unlockPrivateKey(action);
|
||||
|
||||
it('should match put setWallet snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockKeystore*', () => {
|
||||
const action = unlockKeystoreActionGen({
|
||||
file: JSON.stringify(utcKeystore),
|
||||
password: 'testtesttest'
|
||||
});
|
||||
const gen = unlockKeystore(action);
|
||||
|
||||
it('should match put setWallet snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockMnemonic*', () => {
|
||||
const action = unlockMnemonicActionGen({
|
||||
phrase:
|
||||
'first catalog away faculty jelly now life kingdom pigeon raise gain accident',
|
||||
pass: '',
|
||||
path: "m/44'/60'/0'/0/8",
|
||||
address: '0xe2EdC95134bbD88443bc6D55b809F7d0C2f0C854'
|
||||
});
|
||||
const gen = unlockMnemonic(action);
|
||||
|
||||
it('should match put setWallet snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be done', () => {
|
||||
expect(gen.next().done).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockWeb3*', () => {
|
||||
const gen = unlockWeb3();
|
||||
const accounts = [address];
|
||||
|
||||
window.web3 = {
|
||||
eth: {
|
||||
getAccounts: jest.fn(cb => cb(undefined, accounts))
|
||||
},
|
||||
version: {
|
||||
getNetwork: jest.fn(cb => cb(undefined, '1'))
|
||||
},
|
||||
network: '1'
|
||||
};
|
||||
|
||||
beforeAll(async done => {
|
||||
await initWeb3Node();
|
||||
done();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
delete window.web3;
|
||||
});
|
||||
|
||||
it('should call initWeb3Node', () => {
|
||||
expect(gen.next().value).toEqual(call(initWeb3Node));
|
||||
});
|
||||
|
||||
it('should cps web3.eth.getAccounts', () => {
|
||||
expect(gen.next().value).toEqual(cps(window.web3.eth.getAccounts));
|
||||
});
|
||||
|
||||
it('should put changeNodeIntent', () => {
|
||||
expect(gen.next(accounts).value).toEqual(put(changeNodeIntent('web3')));
|
||||
});
|
||||
|
||||
it('should match setWallet snapshot', () => {
|
||||
expect(gen.next().value).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('broadcastTx*', () => {
|
||||
const signedTx = 'signedTx';
|
||||
const txHash = 'txHash';
|
||||
const action = broadcastTxActionGen(signedTx);
|
||||
const gen = broadcastTx(action);
|
||||
const networkConfig = {
|
||||
blockExplorer: 'foo'
|
||||
};
|
||||
let random;
|
||||
|
||||
beforeAll(() => {
|
||||
random = Math.random;
|
||||
Math.random = jest.fn(() => 0.001);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Math.random = random;
|
||||
});
|
||||
|
||||
it('should select getNodeLib', () => {
|
||||
expect(gen.next().value).toEqual(select(getNodeLib));
|
||||
});
|
||||
|
||||
it('should select getNetworkConfig', () => {
|
||||
expect(gen.next(node).value).toEqual(select(getNetworkConfig));
|
||||
});
|
||||
|
||||
it('should apply node.sendRawTx', () => {
|
||||
expect(gen.next(networkConfig).value).toEqual(
|
||||
apply(node, node.sendRawTx, [signedTx])
|
||||
);
|
||||
});
|
||||
|
||||
it('should match put showNotifiction snapshot', () => {
|
||||
expect(gen.next(txHash).value).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should put broadcastTxSucceded', () => {
|
||||
expect(gen.next().value).toEqual(
|
||||
put(broadcastTxSucceded(txHash, signedTx))
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue