MyCrypto/spec/reducers/config/config.spec.ts
William O'Beirne a043334685 Node Refactor (#1603)
* Initial work on refactoring node definitions to reduce number of places theyre defined, amount of copy pasting.

* Use makeAutoNodeNAme instead of manually appending _auto

* Add getNetVersion to list of unsupported methods

* PR feedback

* Rework web template node selector to be a network selector. Refactor some types to help with that. Better handle removing custom nodes.

* Remove color dropdown.

* Fix selecting custom networks. Show notification if change network intent fails.

* Use selectors for current node / network instead of intuiting from nodeSelection

* Add id key to all networks, simplify add and remove custom node and network functions.

* Fix a lot of uses of network.name to use network.id instead.

* Dont allow network chainid conflicts

* Fix web3 network by chainid

* Add testnet badge to network selector

* Change nomenclature from change(Node|Network)(Intent)? to change(Node|Network)(Requested|Succeeded)

* tscheck

* Better code for chainid collision

* Remove console logs

* Fix tests

* Network selector becomes self contained component used both by web header and electron nav.

* Dont select node again

* Additional title text

* tscheck

* Custom node behavior in Electron

* Close panel too

* Convert node label data into selector function

* tscheck

* Parens & space
2018-05-29 09:51:42 -05:00

340 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { configuredStore } from 'store';
import { delay, SagaIterator } from 'redux-saga';
import { call, cancel, fork, put, take, select, apply } from 'redux-saga/effects';
import { cloneableGenerator, createMockTask } from 'redux-saga/utils';
import {
setOffline,
setOnline,
changeNodeSucceeded,
changeNodeRequested,
changeNodeForce,
setLatestBlock,
TypeKeys,
ChangeNodeRequestedOneTimeAction,
changeNodeRequestedOneTime
} from 'actions/config';
import {
handleChangeNodeRequested,
handlePollOfflineStatus,
pollOfflineStatus,
handleNewNetwork,
handleChangeNodeRequestedOneTime
} from 'sagas/config/node';
import {
getNodeId,
getNodeConfig,
getOffline,
isStaticNodeId,
getStaticNodeFromId,
getCustomNodeFromId,
getPreviouslySelectedNode
} from 'selectors/config';
import { Web3Wallet } from 'libs/wallet';
import { showNotification } from 'actions/notifications';
import { translateRaw } from 'translations';
import { StaticNodeConfig } from 'types/node';
import { staticNodesExpectedState } from './nodes/staticNodes.spec';
import { selectedNodeExpectedState } from './nodes/selectedNode.spec';
import { customNodesExpectedState, firstCustomNode } from './nodes/customNodes.spec';
import { unsetWeb3Node, unsetWeb3NodeOnWalletEvent } from 'sagas/config/web3';
import { shepherd } from 'mycrypto-shepherd';
import { getShepherdOffline, getShepherdPending } from 'libs/nodes';
// init module
configuredStore.getState();
describe('pollOfflineStatus*', () => {
const restoreNotif = 'Your connection to the network has been restored!';
const lostNetworkNotif = `Youve lost your connection to the network, check your internet
connection or try changing networks from the dropdown at the
top right of the page.`;
const offlineNotif = 'You are currently offline. Some features will be unavailable.';
const offlineOnFirstTimeCase = pollOfflineStatus();
it('should delay by 2.5 seconds', () => {
expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500));
});
it('should skip if a node change is pending', () => {
expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending));
expect(offlineOnFirstTimeCase.next(true).value).toEqual(call(delay, 2500));
expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending));
});
it('should select offline', () => {
expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline));
});
it('should select shepherd"s offline', () => {
expect(offlineOnFirstTimeCase.next(false).value).toEqual(call(getShepherdOffline));
});
// .PUT.action.payload.msg is used because the action creator uses an random ID, cant to a showNotif comparision
it('should put a different notif if online for the first time ', () => {
expect(offlineOnFirstTimeCase.next(true).value).toEqual(put(setOffline()));
expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual(
offlineNotif
);
});
it('should loop around then go back online, putting a restore msg', () => {
expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500));
expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending));
expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline));
expect(offlineOnFirstTimeCase.next(true).value).toEqual(call(getShepherdOffline));
expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual(
restoreNotif
);
expect(offlineOnFirstTimeCase.next(false).value).toEqual(put(setOnline()));
});
it('should put a generic lost connection notif on every time afterwards', () => {
expect(offlineOnFirstTimeCase.next().value).toEqual(call(delay, 2500));
expect(offlineOnFirstTimeCase.next().value).toEqual(call(getShepherdPending));
expect(offlineOnFirstTimeCase.next(false).value).toEqual(select(getOffline));
expect(offlineOnFirstTimeCase.next(false).value).toEqual(call(getShepherdOffline));
expect(offlineOnFirstTimeCase.next(true).value).toEqual(put(setOffline()));
expect((offlineOnFirstTimeCase.next().value as any).PUT.action.payload.msg).toEqual(
lostNetworkNotif
);
});
});
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('handleChangeNodeRequested*', () => {
let originalRandom: any;
// normal operation variables
const defaultNodeId: any = selectedNodeExpectedState.initialState.nodeId;
const defaultNodeConfig: any = (staticNodesExpectedState as any).initialState[defaultNodeId];
const newNodeId = Object.keys(staticNodesExpectedState.initialState).reduce(
(acc, cur) =>
(staticNodesExpectedState as any).initialState[cur].network !== defaultNodeConfig.network
? cur
: acc
);
const newNodeConfig: StaticNodeConfig = (staticNodesExpectedState as any).initialState[newNodeId];
const isOffline = false;
const changeNodeRequestedAction = changeNodeRequested(newNodeId);
const latestBlock = '0xa';
const data = {} as any;
data.gen = cloneableGenerator(handleChangeNodeRequested)(changeNodeRequestedAction);
function shouldBailOut(gen: SagaIterator, nextVal: any, errMsg: string) {
expect(gen.next(nextVal).value).toEqual(put(showNotification('danger', errMsg, 5000)));
expect(gen.next().done).toEqual(true);
}
beforeAll(() => {
originalRandom = Math.random;
Math.random = () => 0.001;
});
afterAll(() => {
Math.random = originalRandom;
});
it('should select is static node', () => {
expect(data.gen.next().value).toEqual(select(isStaticNodeId, newNodeId));
});
it('should select nodeConfig', () => {
expect(data.gen.next(defaultNodeId).value).toEqual(select(getNodeConfig));
});
it('should select getStaticNodeFromId', () => {
expect(data.gen.next(defaultNodeConfig).value).toEqual(select(getStaticNodeFromId, newNodeId));
});
it('should get the next network', () => {
expect(data.gen.next(newNodeConfig).value).toMatchSnapshot();
});
it('should select isOffline', () => {
expect(data.gen.next(true).value).toEqual(select(getOffline));
});
it('should show error if check times out', () => {
data.clone1 = data.gen.clone();
data.clone1.next(true);
expect(data.clone1.throw('err').value).toEqual(
put(showNotification('danger', translateRaw('ERROR_32'), 5000))
);
expect(data.clone1.next().done).toEqual(true);
});
it('should sucessfully switch to the manual node', () => {
expect(data.gen.next(isOffline).value).toEqual(
apply(shepherd, shepherd.manual, [newNodeId, false])
);
});
it('should get the current block', () => {
data.gen.next();
});
it('should put setLatestBlock', () => {
expect(data.gen.next(latestBlock).value).toEqual(put(setLatestBlock(latestBlock)));
});
it('should put changeNode', () => {
expect(data.gen.next().value).toEqual(
put(changeNodeSucceeded({ networkId: newNodeConfig.network, nodeId: newNodeId }))
);
});
it('should fork handleNewNetwork', () => {
expect(data.gen.next().value).toEqual(fork(handleNewNetwork));
});
it('should be done', () => {
expect(data.gen.next().done).toEqual(true);
});
// custom node variables
const customNodeConfigs = customNodesExpectedState.addFirstCustomNode;
const customNodeAction = changeNodeRequested(firstCustomNode.id);
data.customNode = handleChangeNodeRequested(customNodeAction);
// test custom node
it('should select getCustomNodeConfig and match race snapshot', () => {
data.customNode.next();
data.customNode.next(false);
expect(data.customNode.next(defaultNodeConfig).value).toEqual(
select(getCustomNodeFromId, firstCustomNode.id)
);
expect(data.customNode.next(customNodeConfigs.customNode1).value).toMatchSnapshot();
});
const customNodeIdNotFound = firstCustomNode.id + 'notFound';
const customNodeNotFoundAction = changeNodeRequested(customNodeIdNotFound);
data.customNodeNotFound = handleChangeNodeRequested(customNodeNotFoundAction);
// test custom node not found
it('should handle unknown / missing custom node', () => {
data.customNodeNotFound.next();
data.customNodeNotFound.next(false);
});
it('should select getCustomNodeFromId', () => {
expect(data.customNodeNotFound.next(defaultNodeConfig).value).toEqual(
select(getCustomNodeFromId, customNodeIdNotFound)
);
});
it('should show an error if was an unknown custom node', () => {
shouldBailOut(
data.customNodeNotFound,
null,
`Attempted to switch to unknown node '${customNodeNotFoundAction.payload}'`
);
});
});
describe('handleChangeNodeRequestedOneTime', () => {
const saga = handleChangeNodeRequestedOneTime();
const action: ChangeNodeRequestedOneTimeAction = changeNodeRequestedOneTime('eth_auto');
it('should take a one time action based on the url containing a valid network to switch to', () => {
expect(saga.next().value).toEqual(take(TypeKeys.CONFIG_CHANGE_NODE_REQUESTED_ONETIME));
});
it(`should delay for 10 ms to allow shepherdProvider async init to complete`, () => {
expect(saga.next(action).value).toEqual(call(delay, 100));
});
it('should dispatch the change node intent', () => {
expect(saga.next().value).toEqual(put(changeNodeRequested(action.payload)));
});
it('should be done', () => {
expect(saga.next().done).toEqual(true);
});
});
describe('unsetWeb3Node*', () => {
const previousNodeId = 'eth_mycrypto';
const mockNodeId = 'web3';
const gen = unsetWeb3Node();
it('should select getNode', () => {
expect(gen.next().value).toEqual(select(getNodeId));
});
it('should select an alternative node to web3', () => {
// get a 'no visual difference' error here
expect(gen.next(mockNodeId).value).toEqual(select(getPreviouslySelectedNode));
});
it('should put changeNodeForce', () => {
expect(gen.next(previousNodeId).value).toEqual(put(changeNodeForce(previousNodeId)));
});
it('should be done', () => {
expect(gen.next().done).toEqual(true);
});
it('should return early if node type is not web3', () => {
const gen1 = unsetWeb3Node();
gen1.next();
gen1.next('notWeb3');
expect(gen1.next().done).toEqual(true);
});
});
describe('unsetWeb3NodeOnWalletEvent*', () => {
const fakeAction: any = {};
const mockNodeId = 'web3';
const previousNodeId = 'eth_mycrypto';
const gen = unsetWeb3NodeOnWalletEvent(fakeAction);
it('should select getNode', () => {
expect(gen.next().value).toEqual(select(getNodeId));
});
it('should select an alternative node to web3', () => {
expect(gen.next(mockNodeId).value).toEqual(select(getPreviouslySelectedNode));
});
it('should put changeNodeForce', () => {
expect(gen.next(previousNodeId).value).toEqual(put(changeNodeForce(previousNodeId)));
});
it('should be done', () => {
expect(gen.next().done).toEqual(true);
});
it('should return early if node type is not web3', () => {
const gen1 = unsetWeb3NodeOnWalletEvent({ payload: false } as any);
gen1.next(); //getNode
gen1.next('notWeb3'); //getNodeConfig
expect(gen1.next().done).toEqual(true);
});
it('should return early if wallet type is web3', () => {
const mockAddress = '0x0';
const mockNetwork = 'ETH';
const mockWeb3Wallet = new Web3Wallet(mockAddress, mockNetwork);
const gen2 = unsetWeb3NodeOnWalletEvent({ payload: mockWeb3Wallet } as any);
gen2.next(); //getNode
gen2.next('web3'); //getNodeConfig
expect(gen2.next().done).toEqual(true);
});
});